Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
f46e88f
feat(learn): optional enrollment for LMS integrations (#42)
francescoscalzo Mar 21, 2026
c8a8d1c
chore: bump version to 0.3.0 and update CHANGELOG
fvadicamo Mar 21, 2026
79f8724
style: address PR #43 review comments
fvadicamo Mar 21, 2026
d4c1bf3
docs: update documentation for Phase 2 completion
fvadicamo Mar 21, 2026
a7637b5
style(docs): address PR #43 round 3 review comments
fvadicamo Mar 21, 2026
84684cd
style(docs): remove stale Phase 1/Phase 2 qualifiers from user docs
fvadicamo Mar 21, 2026
b8fe0b0
Merge pull request #43 from vektralabs/release/v0.3.0
fvadicamo Mar 21, 2026
fa03257
chore: sync main into develop to resolve diverged history
fvadicamo Mar 21, 2026
9b503ac
fix(core): restore graceful safeguard fallback lost in main merge
fvadicamo Mar 21, 2026
5298a03
fix: resolve 3 critical issues from PR #45 review
fvadicamo Mar 21, 2026
29f830e
fix(core): add post_retrieval recheck in streaming path
fvadicamo Mar 21, 2026
ceb8957
style: address PR #45 nitpick review comments
fvadicamo Mar 21, 2026
077b349
fix(admin): use helper tail as audit verb to distinguish actions
fvadicamo Mar 22, 2026
ed14094
docs(arch): fix version history chronological order
fvadicamo Mar 22, 2026
d0a7c86
Merge pull request #45 from vektralabs/chore/sync-main-into-develop
fvadicamo Mar 22, 2026
11861af
fix: address PR #44 review comments
fvadicamo Mar 22, 2026
a78d5d9
style(scripts): support .markdown extension in ingest.sh
fvadicamo Mar 22, 2026
a36b191
Merge pull request #46 from vektralabs/fix/pr44-review-fixes
fvadicamo Mar 22, 2026
95c45b5
fix(learn): include namespace in dashboard token audit event
fvadicamo Mar 22, 2026
fad9f39
Merge pull request #47 from vektralabs/fix/pr44-audit-namespace
fvadicamo Mar 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 56 additions & 7 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@
# Generic LLM API key (passed to litellm)
# VEKTRA_LLM_API_KEY=

# Custom API base URL for OpenAI-compatible providers (vLLM, LM Studio, etc.)
# When set, litellm routes requests to this URL instead of the provider default.
# Example: VEKTRA_LLM_API_BASE=http://localhost:8000/v1
# VEKTRA_LLM_API_BASE=

# Or set provider-specific keys directly:
# OPENAI_API_KEY=sk-...
# ANTHROPIC_API_KEY=sk-ant-...
Expand Down Expand Up @@ -53,7 +58,7 @@

# Embedding provider and model.
# VEKTRA_EMBEDDING_PROVIDER=sentence-transformers
# VEKTRA_EMBEDDING_MODEL=all-MiniLM-L6-v2
# VEKTRA_EMBEDDING_MODEL=paraphrase-multilingual-MiniLM-L12-v2

# --------------------------------------------------------------------------
# Vector store
Expand All @@ -62,34 +67,51 @@
# VEKTRA_VECTOR_STORE_PROVIDER=pgvector
# VEKTRA_ACTIVE_INDEX_VERSION=1

# Qdrant settings (only used when VEKTRA_VECTOR_STORE_PROVIDER=qdrant)
# VEKTRA_QDRANT_URL=http://localhost:6333
# VEKTRA_QDRANT_API_KEY=
# VEKTRA_QDRANT_COLLECTION=vektra

# --------------------------------------------------------------------------
# Query pipeline
# --------------------------------------------------------------------------

# VEKTRA_QUERY_PIPELINE=simple
# VEKTRA_QUERY_PIPELINE=advanced
# VEKTRA_MIN_RELEVANCE_SCORE=0.3
# VEKTRA_CHUNK_DEDUP_ENABLED=true
# VEKTRA_RESPONSE_TOKEN_RESERVE=1024
# VEKTRA_RESPONSE_TOKEN_RESERVE=2048
# VEKTRA_CONTEXT_CHUNK_RATIO=0.6
# VEKTRA_PROMPT_TEMPLATES_DIR=

# Query rewriting (AdvancedQueryPipeline)
# VEKTRA_QUERY_REWRITE_ENABLED=true
# VEKTRA_QUERY_REWRITE_MODEL=

# Reranking
# VEKTRA_RERANK_ENABLED=true
# VEKTRA_RERANK_PROVIDER=flashrank
# VEKTRA_RERANK_MODEL=
# VEKTRA_RERANK_TOP_K=5

# --------------------------------------------------------------------------
# Ingestion
# --------------------------------------------------------------------------

# VEKTRA_CHUNKING_STRATEGY=fixed
# VEKTRA_CHUNK_SIZE=1000
# VEKTRA_CHUNK_OVERLAP=200
# VEKTRA_CHUNK_SIZE=500
# VEKTRA_CHUNK_OVERLAP=100
# VEKTRA_MAX_FILE_SIZE_MB=50
# VEKTRA_DOCUMENT_EXTRACTOR=pdfplumber
# VEKTRA_TABLE_SPLIT=false
# VEKTRA_PARENT_CHILD_LEVELS=0

# --------------------------------------------------------------------------
# LLM fallback
# --------------------------------------------------------------------------

# Fallback model when primary times out.
# VEKTRA_LLM_FALLBACK_MODEL=
# VEKTRA_LLM_FALLBACK_TIMEOUT_MS=30000
# VEKTRA_LLM_FALLBACK_TIMEOUT_MS=60000
# VEKTRA_LLM_CONTEXT_ONLY_ENABLED=true

# --------------------------------------------------------------------------
Expand All @@ -99,6 +121,11 @@
# VEKTRA_PORT=8000
# VEKTRA_STARTUP_LLM_CHECK=true

# Optional service ports (host-side). Override to avoid port conflicts.
# OLLAMA_PORT=11434
# QDRANT_PORT=6333
# TEI_PORT=8080

# --------------------------------------------------------------------------
# Observability
# --------------------------------------------------------------------------
Expand All @@ -108,13 +135,35 @@
# VEKTRA_EVAL_MODE=false

# --------------------------------------------------------------------------
# Phase 2 (not active in Phase 1)
# Security
# --------------------------------------------------------------------------

# VEKTRA_SAFEGUARD_MODE=passthrough
# VEKTRA_MULTI_TENANT=false
# VEKTRA_CONVERSATION_KEY=
# VEKTRA_PII_CHUNK_THRESHOLD=3
# VEKTRA_RETENTION_DAYS=
# VEKTRA_ANALYTICS_RETENTION_DAYS=
# VEKTRA_SPARSE_EMBEDDING_PROVIDER=
# VEKTRA_SPARSE_EMBEDDING_MODEL=

# --------------------------------------------------------------------------
# Webhooks
# --------------------------------------------------------------------------

# Webhook endpoint for event delivery. Disabled when unset.
# VEKTRA_WEBHOOK_URL=
# VEKTRA_WEBHOOK_SECRET=
# VEKTRA_WEBHOOK_TIMEOUT=5.0

# --------------------------------------------------------------------------
# Learn (e-learning vertical)
# --------------------------------------------------------------------------

# JWT signing secret for dashboard tokens. Required when vektra-learn is active.
# VEKTRA_LEARN_JWT_SECRET=

# Require Vektra enrollment record for learn queries.
# Set to false when an external LMS (e.g. Moodle) manages enrollment.
# When false, namespace is derived from JWT (namespace claim or course_id fallback).
# VEKTRA_LEARN_REQUIRE_ENROLLMENT=true
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ logs/
.env.*.local
.claude/settings.local.json
CLAUDE.local.md
docker-compose.ports.yml

# Build outputs
dist/
Expand Down
586 changes: 511 additions & 75 deletions .s2s/BACKLOG.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion .s2s/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -2217,5 +2217,5 @@ Quality scenarios (QS-xx) define measurable targets. Validation scenarios ([vali
*Version 1.8 - Configuration reference: ARCH-060 (37 env vars: 35 VEKTRA_* + 2 external, 5 newly named + VEKTRA_MAX_PDF_SIZE renamed to VEKTRA_MAX_FILE_SIZE_MB, startup validation mapping, .env.example)*
*Version 1.9 - Quality scenarios: Section 10 expanded from 12 to 18 QS entries, quality tree restructured, 6 architecture-derived scenarios added (ARCH-043 degradation, ARCH-057 startup, ARCH-039 extensibility, ARCH-040 evolvability, NFR-008 retention, NFR-010 progress), validation scenario cross-reference (10.4)*
*Version 1.9.1 - Conversational query rewriting: ARCH-061 (pre-retrieval query rewriting in AdvancedQueryPipeline), ADR-0023, rewrite.j2 template added to ARCH-054, ARCH-036 Phase 2 updated. Multilingual embedding note added to ADR-0013.*
*Version 2.0 - Phase 2 delivery annotations: all Phase 2 features marked as implemented (v0.2.0). Component table expanded with vektra-analytics and vektra-learn. Protocol implementations updated: QdrantVectorStoreProvider, UnstructuredExtractor, DualStrategyChunking, AdvancedQueryPipeline, FastEmbedBM25Provider, LogEventEmitter. WebhookEventEmitter deferred to Phase 3.*
*Version 1.10 - OQ-018/OQ-019 resolution: ARCH-062 (admin UI server-side rendering, ADR-0024), ARCH-063 (learn chatbot widget, ADR-0025), ARCH-064 (Phase 2 hardware target 8GB/4CPU). Glossary: HTMX added. Deferred table: 3 entries added.*
*Version 2.0 - Phase 2 delivery annotations: all Phase 2 features marked as implemented (v0.2.0). Component table expanded with vektra-analytics and vektra-learn. Protocol implementations updated: QdrantVectorStoreProvider, UnstructuredExtractor, DualStrategyChunking, AdvancedQueryPipeline, FastEmbedBM25Provider, LogEventEmitter. WebhookEventEmitter deferred to Phase 3.*
80 changes: 59 additions & 21 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,72 @@ 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/).

## [0.2.0] - 2026-03-14
## [0.3.0] - 2026-03-21

Phase 2: advanced RAG features, e-learning vertical, and production hardening.
E-learning vertical refinements, widget UX improvements, and Phase 2 stabilization.

### Added

- **vektra-core**: AdvancedQueryPipeline with conversational query rewriting (ARCH-061), cross-encoder reranking via `rerankers` library, hybrid search orchestration. Persistent encrypted conversations (pgcrypto). Response feedback collection (response_id, citation_id). Presidio PII safeguard with content modification. Composable Jinja2 prompt templates (system, context, conversation, rewrite)
- **vektra-ingest**: OCR support via UnstructuredExtractor (Tesseract, optional INSTALL_UNSTRUCTURED build arg). DualStrategyChunking (text split with overlap, tables never split, parent-child hierarchy). Document versioning (re-ingest creates new version, soft-deletes old). Batch delete API. Markdown extractor. Granular pipeline APIs (extract, chunk, embed endpoints)
- **vektra-index**: QdrantVectorStoreProvider with native dense/sparse/hybrid search. FastEmbedBM25Provider for sparse embeddings. Hybrid search via Reciprocal Rank Fusion (RRF). Zero-downtime reindex API with index version management
- **vektra-analytics** (new component): QueryTrace dedicated storage, metrics aggregation (latency, retrieval scores, model distribution, throughput), reporting API with namespace filtering
- **vektra-learn** (new component): LMS-agnostic e-learning vertical with enrollment management, course-scoped content ingestion, JWT dashboard token generation, course-scoped RAG query endpoint. Backend-served chatbot widget (esbuild IIFE bundle) with streaming, source citations, light/dark themes, i18n (en/it)
- **vektra-admin**: HTMX + Jinja2 admin dashboard (health, keys, namespaces, audit, config pages). Per-key rate limiting (rate_limit_rpm). Granular scope enforcement (admin/ingest/query via require_scope middleware). Namespace quota management
- **Database**: 4 new migrations (Phase 2 tables, RLS policies, hybrid search indexes, learn tables). PostgreSQL RLS policies for namespace isolation. TOCTOU fix on API key creation
- **Infrastructure**: Docker multi-stage build with widget-builder stage. Qdrant Docker Compose profile (--profile qdrant). TEI embedding server profile (--profile tei). INSTALL_UNSTRUCTURED build arg for optional OCR. Torch CPU-only optimization
- **Protocols**: SparseEmbeddingProvider, extended VectorStoreProvider (SearchMode, full-store contract), ChunkingStrategy, extended SafeguardHook (content modification), LogEventEmitter
- **Configuration**: 11 new env vars (rewrite, rerank, webhook, ingest extensions, learn JWT). 48 total VEKTRA_* variables
- **ADRs**: ADR-0022 (SQLAlchemy async), ADR-0023 (query rewriting), ADR-0024 (admin UI server-side), ADR-0025 (chatbot widget)
- **vektra-learn**: optional enrollment mode (`VEKTRA_LEARN_REQUIRE_ENROLLMENT`) for LMS integrations where enrollment sync is not yet configured
- **vektra-learn**: collapsible source citations in chatbot widget with accessibility (aria-controls, focus-visible)
- **vektra-learn**: i18n support for source fallback labels (en/it)
- **vektra-core**: `VEKTRA_LLM_API_BASE` config for OpenAI-compatible providers (vLLM, etc.)
- **vektra-ingest**: sparse embedding count validation (fail-fast, consistent with dense path)
- **docker-compose**: parameterized host ports for optional services (Qdrant, TEI)

### Fixed

- Qdrant point IDs: use uuid5(doc_id, chunk_index) for deterministic UUID generation
- Qdrant collection auto-creation at startup via ensure_collection()
- no_relevant_context detection: trigger on zero filtered results (not only when search returns results)
- Retrieval filter deduplication: preserve score-descending order
- Learn enrollment IntegrityError: detect FK violation via sqlstate/pgcode (not string matching)
- Learn content/ingest: SSRF mitigation with per-hop DNS re-validation on redirects
- Ingest namespace: auto-create namespace record on first ingest (pg upsert)
- Ingest status: return "new" (not "indexed") for successfully ingested documents
- **vektra-learn**: auto-generate conversation_id for multi-turn continuity (BUG-010)
- **vektra-learn**: validate course_id claim in JWT before namespace resolution
- **vektra-learn**: show fallback message when no relevant context found
- **vektra-learn**: destructure onNoRelevantContext callback in widget API client
- **vektra-ingest**: generate sparse embeddings for hybrid search (BUG-011)
- **vektra-admin**: guard uninitialized registry in health check (prevents 500 during startup)
- **vektra-core**: pass registry to qdrant_check startup validation step (ARCH-057)
- **widget**: fix snippet truncation and ellipsis detection
- **widget**: improve source citation readability

### Changed

- Default configuration updated to Combo D (RAG tuning report winning config): advanced pipeline, paraphrase-multilingual-MiniLM-L12-v2 embeddings, 2048 response token reserve, 60s fallback timeout
- Streaming responses now emit QueryTrace via SSE (DEBT-002)
- post_retrieval safeguard boundary now called in both execute and stream paths (DEBT-003)

### Security

- API key verification uses TTLCache instead of plaintext LRU cache (DEBT-008)

## [0.2.0] - 2026-03-15

Phase 2: advanced RAG pipeline, multi-tenant isolation, hybrid search, analytics, admin UI, and e-learning vertical.

### Added

- **vektra-shared**: SparseEmbeddingProvider and SparseVector protocols, WebhookEventEmitter, Phase 2 config fields (rewrite, rerank, webhook, learn)
- **vektra-core**: AdvancedQueryPipeline with query rewriting, cross-encoder reranking, and hybrid search routing; Presidio-based PII safeguard with content modification; persistent conversations with pgcrypto encryption; feedback and citation-feedback APIs; streaming QueryTrace
- **vektra-ingest**: OCR support via Unstructured extractor; dual-strategy chunking (text + table preservation); batch ingestion and deletion; Markdown extraction; document version tracking; phase-aware job lifecycle
- **vektra-index**: Qdrant vector store provider; BM25 sparse embeddings via fastembed; hybrid search (dense + sparse); reindex API with atomic version switching
- **vektra-admin**: PostgreSQL RLS enforcement for namespace isolation; scope checking (admin/ingest/query/monitor); rate-limiting middleware; TTL cache for key verification; HTMX + Jinja2 admin dashboard (ADR-0024) with health, keys, namespaces, and audit views
- **vektra-analytics**: QueryTrace storage and retrieval; per-namespace metrics aggregation; retention cleanup
- **vektra-learn**: LMS-agnostic API for enrollment, token generation, and course-scoped queries; JWT-based course isolation; chatbot widget as backend-served JS bundle (ADR-0025)
- **Infrastructure**: TEI and Qdrant Docker Compose profiles; reindex and batch-ingest operator scripts; 11-step startup validation sequence (ARCH-057)
- **Database**: 4 new migrations (Phase 2 tables, RLS policies, hybrid search columns, learn tables)
- **ADRs**: ADR-0022 (SQLAlchemy async), ADR-0023 (query rewriting), ADR-0024 (admin UI), ADR-0025 (learn widget)

### Fixed

- Safeguard pre_query/post_retrieval/pre_response boundaries fully wired
- Conversation persistence made best-effort (non-blocking)
- Qdrant collection creation race condition handled
- Namespace binding enforced on stats and reindex endpoints
- Audit pagination cursor with microsecond precision
- Non-object JSON guard in RLS namespace resolution

### Changed

- Embedding model default: all-MiniLM-L6-v2 to paraphrase-multilingual-MiniLM-L12-v2
- Query pipeline default: simple to advanced
- pydantic capped to <3 across all components

## [0.1.0] - 2026-02-27

Expand Down
48 changes: 40 additions & 8 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ uv run pre-commit install

# Run all unit tests
uv run pytest vektra-shared/tests/ vektra-core/tests/ vektra-ingest/tests/ \
vektra-index/tests/ vektra-admin/tests/ -v -m "not integration"
vektra-index/tests/ vektra-admin/tests/ vektra-analytics/tests/ \
vektra-learn/tests/ -v -m "not integration"
```

## Development tools
Expand Down Expand Up @@ -95,6 +96,34 @@ chore(ci): add path filtering to unit test workflow
All commits must be signed off with a DCO (`git commit -s`). This certifies you have the
right to submit the code under the project's license.

## Commit signing

Branch protection on `develop` and `main` requires **cryptographically signed commits**.
GitHub will block all merge types (merge, squash, rebase) if any source commit is unsigned.

**Set up before your first commit** (recovering unsigned commits is painful):

```bash
# 1. Configure git to sign with your SSH key
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/<your-key>.pub
git config --global commit.gpgsign true

# 2. Register the key on GitHub as BOTH Authentication AND Signing key
# Settings > SSH and GPG keys > New SSH key (select type for each)

# 3. (Optional) Enable local signature verification
echo "$(git config user.email) $(cat ~/.ssh/<your-key>.pub)" > ~/.ssh/allowed_signers
git config --global gpg.ssh.allowedSignersFile ~/.ssh/allowed_signers

# 4. Test
git commit --allow-empty -m "chore: verify signing setup"
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.

## PR workflow

1. Create a branch: `git checkout -b feat/core-streaming`
Expand Down Expand Up @@ -146,15 +175,18 @@ Two AI reviewers comment on PRs automatically:

## Module boundaries

The five components are strictly isolated. Cross-component imports are enforced by
The eight components are strictly isolated. Cross-component imports are enforced by
import-linter and will fail CI:

```text
vektra_shared ← everything may import this
vektra_core ← no imports from admin/ingest/index
vektra_ingest ← no imports from admin/core/index
vektra_index ← no imports from admin/core/ingest
vektra_admin ← no imports from core/ingest/index
vektra_shared <- everything may import this
vektra_core <- no imports from admin/ingest/index/analytics/learn
vektra_ingest <- no imports from admin/core/index/analytics/learn
vektra_index <- no imports from admin/core/ingest/analytics/learn
vektra_admin <- no imports from core/ingest/index/analytics/learn
vektra_analytics <- no imports from core/ingest/index/admin/learn
vektra_learn <- no imports from core/ingest/index/admin/analytics
vektra_app <- may import all (assembly layer)
```

If a component needs functionality from another, it should go through a Protocol interface
Expand All @@ -181,5 +213,5 @@ By participating in this project you agree to abide by our [Code of Conduct](COD

---

*Last updated: 2026-02-27. Contribution guidelines derived from roundtable session
*Last updated: 2026-03-21. Contribution guidelines derived from roundtable session
20260128-roundtable-vektra (REQ-016, REQ-011) and CI/CD setup discussion 20260219.*
2 changes: 1 addition & 1 deletion GOVERNANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ During **Phases 1 and 2** (core development), Vektra follows a Benevolent Dictat
**Transition criteria**: The project will transition to a maintainer committee when:
- There are 3+ active maintainers with 6+ months of consistent contribution
- The contributor base has grown beyond the founding team
- Core interfaces have stabilized (Phase 1 complete, Phase 2 in progress)
- Core interfaces have stabilized (Phase 1 complete, Phase 2 complete)

## Contributor Ladder

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ See [ROADMAP.md](ROADMAP.md) for the full development plan.
| Phase | Focus | Status |
|-------|-------|--------|
| Phase 1 | MVP (core + ingest + index + minimal admin) | Complete |
| Phase 2 | Verticals (learn + moodle + analytics) | Planned |
| Phase 2 | Verticals (learn + moodle + analytics) | Complete |
| Phase 3 | Ecosystem (SDKs, plugins) | Planned |

## Documentation
Expand Down
6 changes: 3 additions & 3 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ services:
volumes:
- vektra_ollama:/root/.ollama
ports:
- "127.0.0.1:11434:11434"
- "127.0.0.1:${OLLAMA_PORT:-11434}:11434"
healthcheck:
test: ["CMD", "curl", "--fail", "http://localhost:11434/api/tags"]
interval: 30s
Expand All @@ -92,7 +92,7 @@ services:
volumes:
- vektra_qdrant:/qdrant/storage
ports:
- "127.0.0.1:6333:6333"
- "127.0.0.1:${QDRANT_PORT:-6333}:6333"
healthcheck:
test: ["CMD", "curl", "--fail", "http://localhost:6333/healthz"]
interval: 10s
Expand All @@ -115,7 +115,7 @@ services:
volumes:
- vektra_tei_cache:/data
ports:
- "127.0.0.1:8080:8080"
- "127.0.0.1:${TEI_PORT:-8080}:8080"
healthcheck:
test: ["CMD", "curl", "--fail", "http://localhost:8080/health"]
interval: 10s
Expand Down
Loading
Loading