Secure enterprise knowledge platform where organizations upload project documents, technical documentation, support records, API specifications, architecture documents, and operational guides. Users later ask questions in Turkish or English and receive answers grounded in authorized company documents.
Current scope: identity, multi-tenancy, RBAC, audit logging, document upload/storage, parsing, semantic chunking, multilingual embeddings (Qdrant), background ingestion workers, hybrid RAG answering, multi-turn enterprise chat (streaming, memory, feedback, and analytics), Phase 4 administration/governance, Phase 5 production engineering (Docker/Helm/Terraform, metrics, rate limits, CI/CD, backups, runbooks), and Phase 6 Next.js enterprise web UI (
frontend/web) are implemented.Authentication: identity is resolved via development-only HTTP headers (see Development identity headers), gated off in
staging/production. It is not a substitute for real authentication (OIDC/SSO) — see Auth roadmap.
- Upload and govern enterprise documents
- Enforce authorization boundaries over knowledge
- Retrieve grounded context from approved sources
- Answer questions in Turkish and English
- Provide auditability and operational visibility
- FastAPI application factory and lifespan
- Modular Clean Architecture layout
- Async PostgreSQL + SQLAlchemy 2 + Alembic
- Redis, Qdrant, and MinIO infrastructure wiring
- Health, readiness, and system info endpoints
- Structured logging and correlation IDs
- Docker / Docker Compose
- Organization multi-tenancy — organizations, memberships, and every business
entity scoped by
organization_id - Scoped RBAC — system + custom roles, permissions, org/project/knowledge-space-scoped role assignments
- Development identity — header-based caller identity for local/test/development only
- Customers, projects, and knowledge spaces — core tenant business entities, with
knowledge-space visibility rules (
organizationvsrestricted) - Append-only audit trail — every mutation is durably recorded with sanitized metadata
- Document pipeline — MinIO upload/storage, PDF/DOCX/HTML/Markdown parsing, semantic chunking, multilingual embeddings into Qdrant
- Background ingestion workers — Redis-backed jobs that run parse → chunk → embed with
retries and failed-job recovery (
make worker/ Composeingestion-worker) - Hybrid RAG — dense (Qdrant) + BM25 lexical retrieval, reranking, provider-neutral LLM answering with citations, prompt versioning, and injection-resistant context assembly
- Enterprise chat — multi-turn conversations grounded via
RagQueryService, pluggable memory strategies, SSE streaming with cooperative cancellation, message idempotency, per-message knowledge-space revalidation, feedback, export (JSON/Markdown), and usage analytics - Administration & governance (Phase 4) —
/api/v1/admin/*dashboard, user list, org quotas/settings, custom role permissions, document/ingestion ops, audit export, usage/token cost analytics, prompt versioning, LLM provider configs (masked secrets), feature flags (Redis cache), ops overview, and retention policies/worker (contextforge-retention-worker) - Production engineering (Phase 5) — hardened Docker/Compose (incl. retention +
obsprofile), security headers, rate limiting, Prometheus/metrics, Helm chart, provider-neutral Terraform stubs, CD to GHCR, backups/DR scripts, k6 scenarios, and ops runbooks underops/runbooks/ - Enterprise web UI (Phase 6) — Next.js 15 / React 19 app in
frontend/webwith App Router, TanStack Query + Zustand, i18n (EN/TR), light/dark theme, chat SSE, documents, knowledge spaces, admin, analytics, and system ops pages - Pytest (unit, integration, architecture, authorization, security, API)
- Ruff, mypy, pre-commit, GitHub Actions CI/CD
The production web client lives in frontend/web.
flowchart LR
Browser[Browser] --> Web[Next.js web :3001]
Web -->|X-ContextForge-* headers| API[FastAPI :8000]
API --> PG[(PostgreSQL)]
API --> Qdrant[(Qdrant)]
API --> MinIO[(MinIO)]
- Next.js 15 (App Router), React 19, TypeScript strict
- Tailwind CSS + shadcn-style Radix primitives
- TanStack Query (server state), Zustand (session/UI)
- React Hook Form + Zod, Axios, i18next (EN/TR), next-themes
- Vitest unit tests, Playwright e2e scaffold
# API + infra
make up
make bootstrap-dev
# Frontend
cd frontend/web
cp config/.env.example .env.local
npm install
npm run dev # http://localhost:3001Sign in with bootstrap presets (admin@contextforge.local / developer@contextforge.local)
or any valid user UUID after bootstrap. The UI stores a client session and sends the
backend development identity headers on every API call.
Compose also builds the web service on port 3001 (NEXT_PUBLIC_API_BASE_URL
should point at the browser-reachable API URL, typically http://localhost:8000).
cd frontend/web
npm run lint
npm run type-check
npm test
npm run build
docker build -t contextforge-web .| Area | Routes |
|---|---|
| Auth | /login, /logout, /forgot-password, /reset-password, /session-expired, /unauthorized |
| Chat | /chat, /chat/[conversationId] (SSE streaming) |
| Documents | /documents, /documents/upload |
| Knowledge spaces | /knowledge-spaces, /new, /[id] |
| Tenancy | /customers, /projects |
| Admin | /admin/* users, roles, prompts, LLM, flags, audit, retention… |
| Analytics / System | /analytics, /system, /settings |
CORS must allow http://localhost:3001 and the X-ContextForge-* headers (wired in
API middleware + config/.env.example).
Screenshots live under docs/screenshots/ui/. Regenerate them with the
API + web running (:8001 / :3001 in local hybrid mode, or compose ports):
cd frontend/web
npm install
npx playwright install chromium # once
node scripts/capture-ui-screenshots.mjs- Open
http://localhost:3001/login. - Click Use bootstrap account / Dev Admin or Dev Developer for instant local sign-in
(no password; client session +
X-ContextForge-*headers). - Or type an email/user UUID, wait for organizations to load, pick an org, then Sign in.
- Use Forgot session? to reach the recovery flow.
- Enter email or user ID.
- Submit to continue to reset (dev UI only — no real email).
- Confirm identity from the query string / form.
- Complete the form to return to login with a fresh client session path.
Shown when the client session TTL expires. Click through to /login and sign in again.
Shown when the signed-in user lacks the permission for a route. Switch account/org or ask an admin for the required RBAC permission.
- Sidebar → Chat.
- Click New conversation to create a thread (optionally scoped to the session knowledge space).
- Search by title; filter Active / Archived.
- Open a row to enter the conversation workspace.
- Row menu: pin / archive / restore / delete (permission-gated).
- Type a question in the composer and Send (SSE streaming reply).
- Open Sources to inspect RAG citations for the latest answer.
- Use Export to download the transcript.
- Switch threads from the left list without leaving chat.
- Sidebar → Documents.
- Filter by search, status, and knowledge space.
- Track lifecycle badges: document status, Parse, Embedding.
- Row actions / bulk select for delete (and admin bulk ops when permitted).
- Click Upload documents to start an upload.
- Select a Knowledge space.
- Drag-and-drop or pick files in the dropzone.
- After upload, return to the documents list and wait for parse/embed jobs.
- Sidebar → Knowledge Spaces.
- Search / filter by status.
- Open a row for detail, or Create for a new space.
- Fill Name, Slug, optional Description.
- Set Visibility and optional Project.
- Create → detail page; Cancel returns to the list.
- Overview: review slug/visibility; edit name, description, visibility → Save.
- Members: invite/remove members and roles for the space.
- Documents: jump into documents scoped to this space.
- Statistics: usage counters for the space.
- Archive / Back from the header actions.
- Open
/customers(permissioncustomer:read). - Review customer code, name, and status in the table.
- Open
/projects(permissionproject:read). - Review project rows for the current organization.
- Sidebar → Analytics.
- Change the time window (e.g. 7 / 30 days).
- Inspect usage, token, and chat charts.
- Export downloads analytics data when available.
- Sidebar → System.
- Check live/ready health and ops counters.
- Filter ingestion jobs by status; refresh to poll worker progress.
- Retry/cancel failed jobs when actions are offered.
- Sidebar → Settings.
- Update display name and preferred language → save.
- Switch theme (light / dark / system) and organization context.
- Header controls also toggle language and theme globally.
- Sidebar → Administration (needs
admin:dashboard). - Read membership, document, conversation, and token stats.
- Use the admin sub-nav for users, orgs, roles, prompts, LLM, flags, settings, audit, retention.
- Drill into ingestion / ops cards when investigating pipeline health.
- Search / filter users by status.
- Activate or Deactivate accounts from row actions.
- List organizations for the platform.
- Open or update org metadata according to available admin actions.
- Review role definitions and permission sets.
- Create/update roles when
role:manage/ admin role permissions allow it.
- Browse prompt templates used by RAG/chat.
- Edit or version prompts where the admin UI exposes mutations.
- Inspect configured LLM providers and models.
- Enable/disable or adjust provider settings for the org.
- Toggle feature flags for staged rollouts.
- Save changes and verify UI/API behavior for the flag.
- Filter audit events by actor/action/time.
- Inspect rows for compliance / incident review (
audit:read).
- Review retention policies for conversations/documents.
- Update retention windows when
admin:retentionis granted.
- Adjust organization-level admin settings.
- Save and confirm via toast / refreshed values.
Typical happy path: sign in as Dev Admin → create/select a knowledge space → upload a document → wait until parse/embed are healthy on System/Documents → open Chat → New conversation → ask a question → open Sources for citations.
flowchart LR
User[User / Client] --> API[ContextForge API]
API --> PG[(PostgreSQL)]
API --> Redis[(Redis)]
API --> Qdrant[(Qdrant)]
API --> MinIO[(MinIO)]
Worker[Ingestion Worker] --> PG
Worker --> Redis
Worker --> Qdrant
Worker --> MinIO
Conceptual layers:
- API — HTTP transport, middleware, schemas
- Application — use cases and ports
- Domain — entities and domain errors
- Infrastructure — PostgreSQL, Redis, Qdrant, MinIO adapters
sequenceDiagram
participant Client
participant API
participant Hybrid as HybridRetrieval
participant Rerank
participant LLM
Client->>API: POST /rag/query
API->>API: rag:query + KS authz
API->>Hybrid: dense + BM25 fuse
Hybrid-->>API: candidates
API->>Rerank: reorder top-N
Rerank-->>API: context chunks
API->>LLM: system + untrusted context + question
LLM-->>API: answer
API-->>Client: answer + citations + diagnostics
sequenceDiagram
participant Client
participant API as Chat router
participant ChatSvc as ChatService
participant Memory as MemoryService
participant Rag as RagQueryService
Client->>API: POST /conversations/{id}/messages
API->>ChatSvc: send_message
ChatSvc->>Memory: build_history_context
ChatSvc->>Rag: query(..., history_context)
Rag-->>ChatSvc: answer + citations
ChatSvc-->>API: ChatAnswer
API-->>Client: user + assistant messages
Every business entity in ContextForge is scoped to an organization (the tenant
boundary). A user can be a member of multiple organizations; each
OrganizationMembership is where roles are actually assigned, and where authorization
for a given request is resolved from.
erDiagram
USER ||--o{ ORGANIZATION_MEMBERSHIP : "has"
ORGANIZATION ||--o{ ORGANIZATION_MEMBERSHIP : "has"
ORGANIZATION ||--o{ CUSTOMER : "owns"
ORGANIZATION ||--o{ PROJECT : "owns"
ORGANIZATION ||--o{ KNOWLEDGE_SPACE : "owns"
ORGANIZATION ||--o{ ROLE : "defines (custom roles)"
CUSTOMER ||--o{ PROJECT : "has"
PROJECT ||--o{ KNOWLEDGE_SPACE : "may contain"
ORGANIZATION_MEMBERSHIP ||--o{ ROLE_ASSIGNMENT : "granted"
ROLE ||--o{ ROLE_ASSIGNMENT : "used in"
ROLE ||--o{ ROLE_PERMISSION : "bundles"
PERMISSION ||--o{ ROLE_PERMISSION : "granted by"
ORGANIZATION_MEMBERSHIP ||--o{ KNOWLEDGE_SPACE_MEMBERSHIP : "granted"
KNOWLEDGE_SPACE ||--o{ KNOWLEDGE_SPACE_MEMBERSHIP : "has"
ORGANIZATION ||--o{ AUDIT_EVENT : "scopes"
USER {
uuid id
string email
string status
bool is_platform_admin
}
ORGANIZATION {
uuid id
string slug
string status
}
ORGANIZATION_MEMBERSHIP {
uuid id
uuid organization_id
uuid user_id
string status
}
ROLE {
uuid id
string code
uuid organization_id "null for system roles"
bool is_system
}
ROLE_ASSIGNMENT {
uuid id
uuid membership_id
uuid role_id
uuid project_id "nullable scope"
uuid knowledge_space_id "nullable scope"
}
CUSTOMER {
uuid id
uuid organization_id
string code
}
PROJECT {
uuid id
uuid organization_id
uuid customer_id
string key
}
KNOWLEDGE_SPACE {
uuid id
uuid organization_id
uuid project_id "nullable"
string visibility
}
KNOWLEDGE_SPACE_MEMBERSHIP {
uuid id
uuid knowledge_space_id
uuid membership_id
string access_level
}
AUDIT_EVENT {
uuid id
uuid organization_id
uuid actor_user_id
string action
string resource_type
}
Request-scoped authorization flow:
sequenceDiagram
participant Client
participant API as FastAPI dependency
participant IdentitySvc as identity_context_service
participant DB as PostgreSQL
Client->>API: Request + X-ContextForge-User-ID / -Organization-ID
API->>IdentitySvc: build_request_context(user_id, organization_id)
IdentitySvc->>IdentitySvc: development_identity_enabled(settings)?
alt disabled (staging/production)
IdentitySvc-->>Client: 401 AUTHENTICATION_REQUIRED
else enabled (local/test/development)
IdentitySvc->>DB: load user, organization, membership
IdentitySvc->>IdentitySvc: validate active status (user/org/membership)
IdentitySvc->>DB: load org/project/KS-scoped permissions + accessible ids
IdentitySvc-->>API: RequestContext (permissions, accessible ids)
API->>API: service.method(uow, ctx, ...)
API->>API: ctx.require_permission(...) / ctx.require_*_access(...)
API-->>Client: 200 (or 403/404 per authorization result)
end
| Area | Choice |
|---|---|
| Language | Python 3.13 |
| API | FastAPI + Uvicorn |
| Settings | pydantic-settings |
| DB | PostgreSQL + SQLAlchemy 2 + asyncpg + Alembic |
| Cache / coordination | Redis |
| Vector store (future) | Qdrant |
| Object storage (future docs) | MinIO |
| Packaging | uv |
| Quality | Ruff, mypy, pytest, pre-commit |
src/contextforge/ Application source
migrations/ Alembic migrations
tests/ Unit, integration, architecture tests
infrastructure/ Docker/service helper assets
docs/ Architecture docs and ADRs
scripts/ Entrypoint and utility scripts
- Python 3.13
- uv
- Docker and Docker Compose
- GNU Make (optional but recommended)
cp config/.env.example .env
make installdocker compose up --buildThis starts:
apion http://localhost:8000ingestion-worker(background document processing)postgreson localhost:5432redison localhost:6379qdranton localhost:6333minioon localhost:9000 (console on 9001)- one-shot
migrateandminio-initjobs
API docs (local/development): http://localhost:8000/docs
Stop:
make down
# or
docker compose downSee config/.env.example. Nested settings use:
CONTEXTFORGE_APP_ENVIRONMENT
CONTEXTFORGE_POSTGRES_HOST
CONTEXTFORGE_REDIS_URL
CONTEXTFORGE_QDRANT_URL
CONTEXTFORGE_MINIO_ENDPOINT
CONTEXTFORGE_CHAT_MEMORY_STRATEGY
CONTEXTFORGE_CHAT_MAX_MESSAGE_LENGTH
CONTEXTFORGE_CHAT_STREAM_HEARTBEAT_SECONDS
Supported environments: local, test, development, staging, production.
Docker Compose uses clearly marked non-production development credentials.
⚠️ This is not production authentication. It is a deliberate, environment-gated stand-in used only inlocal,test, anddevelopment. It is unconditionally disabled instagingandproduction— every request without real authentication is rejected with401 AUTHENTICATION_REQUIREDin those environments, regardless of headers sent.
Every authenticated endpoint resolves "who is calling, and on behalf of which organization" from two request headers:
X-ContextForge-User-ID: <uuid of an existing, active user>
X-ContextForge-Organization-ID: <uuid of an organization the user is an active member of>
Both are validated against the database on every request (user active, organization not
archived, membership active) — a syntactically valid but nonexistent/inactive id is
rejected the same as a missing header. There is no X-ContextForge-Role or
X-ContextForge-Permissions header — permissions are always computed server-side from
RoleAssignment rows; any role/permission header a client sends is simply ignored (see
tests/security/test_role_headers_ignored.py).
make bootstrap-dev seeds a deterministic local development tenant — safe to run
repeatedly (idempotent: looks up each entity by its natural key before creating it, and
produces the exact same UUIDs every time via uuid5):
make migrate # apply migrations first
make bootstrap-devIt creates:
- organization
contextforge-dev - an
organization_adminuser (admin@contextforge.local) and adeveloperuser (developer@contextforge.local), both active members - a customer (
DEV-CUST) and a project (DEMO) linked to it - an organization-visible knowledge space (
company-handbook) and arestrictedone (incident-playbooks), with the developer grantedcontributoraccess to the restricted space
It prints the header values for the seeded admin user at the end:
X-ContextForge-User-ID: <admin uuid>
X-ContextForge-Organization-ID: <org uuid>
Paste those into the curl examples below, or into the Authorize-adjacent headers of
/docs, to call the API as that user. make seed-system-data is a separate, read-only
sanity check that the RBAC permission/system-role reference catalog (seeded by migrations,
not by this script) is actually present, and prints their counts.
make migrate # alembic upgrade head
make migration name="desc" # autogenerate revision
make downgrade # alembic downgrade -1
make worker # run ingestion worker locally
uv run alembic historyCompose applies migrations through the dedicated migrate service before the API starts.
make test
make test-unit
make test-integration
make test-architecture
make test-authorization
make test-security
make coverageIntegration tests expect local infrastructure (Compose) to be reachable on the default ports.
test-authorization and test-security run the authorization- and security-marked
suites under tests/unit/authorization/ and tests/security/ respectively (plus any other
test marked accordingly); tests/api/ is marked api and exercised as part of make test.
make lint
make format
make type-check| Method | Path | Purpose |
|---|---|---|
| GET | /api/v1/health/live |
Liveness (no infra dependency) |
| GET | /api/v1/health/ready |
Readiness for PostgreSQL, Redis, Qdrant, MinIO |
| GET | /api/v1/system/info |
Safe system metadata and capability flags |
| POST/GET | /api/v1/organizations, /{id}, /{id}/suspend, /{id}/archive |
Organization lifecycle |
| POST | /api/v1/users, /{id}, /{id}/suspend, /{id}/archive |
User provisioning/lifecycle |
| POST/GET/DELETE | /api/v1/memberships, /{id}, /{id}/suspend |
Organization membership lifecycle |
| GET/POST/PATCH/DELETE | /api/v1/roles, /{id}, /assignments, /assignments/{id} |
Roles and role assignments |
| POST/GET/PATCH | /api/v1/customers, /{id}, /{id}/archive |
Customer lifecycle |
| POST/GET/PATCH | /api/v1/projects, /{id}, /{id}/archive |
Project lifecycle |
| POST/GET/PATCH | /api/v1/knowledge-spaces, /{id}, /{id}/archive |
Knowledge space lifecycle |
| POST/GET/PATCH/DELETE | /api/v1/knowledge-spaces/{id}/memberships, /{ks_membership_id} |
Knowledge-space membership |
| POST/GET/PATCH/PUT/DELETE | /api/v1/documents, /{id}, /{id}/content, /{id}/download |
Document upload, metadata, content replace, download, delete |
| POST/GET | /api/v1/documents/{id}/parse, /{id}/chunks, /{id}/embeddings |
Parse, chunk, and embed a document on demand |
| GET/POST | /api/v1/ingestion-jobs, /{id}, /{id}/retry, /documents/{id}/ingestion-jobs |
Background ingestion jobs (list, inspect, retry failed) |
| POST | /api/v1/rag/search, /rag/query, /rag/query/stream |
Hybrid retrieval and grounded RAG answers (rag:query) |
| GET/POST/PATCH/DELETE | /api/v1/conversations, /{id}, /{id}/archive, /{id}/restore |
Conversation lifecycle (chat:use) |
| GET/POST/DELETE | /api/v1/conversations/{id}/participants, /{user_id} |
Conversation participant management |
| POST | /api/v1/conversations/{id}/messages, /messages/stream, /messages/{id}/cancel |
Send a chat message (sync or SSE stream) and cancel an in-flight stream |
| GET | /api/v1/conversations/{id}/messages, /suggestions, /export |
List messages, follow-up suggestions, JSON/Markdown export |
| GET/PUT | /api/v1/messages/{id}, /{id}/feedback |
Read a message and submit/update feedback |
| GET | /api/v1/chat/analytics/overview |
Chat usage and quality analytics (chat:manage) |
| GET | /api/v1/audit |
Query the append-only audit trail (audit:read) |
| GET | /api/v1/admin/dashboard |
Org administration dashboard (admin:dashboard) |
| GET/POST | /api/v1/admin/users, /users/{id}/activate|deactivate |
Admin user list and activation (admin:users) |
| GET/PATCH | /api/v1/admin/organizations/settings, /settings |
Quotas, defaults, feature overrides (admin:organizations / admin:settings) |
| PUT/DELETE | /api/v1/admin/roles/{id}/permissions, /roles/{id} |
Custom role permission replace / archive (admin:roles) |
| GET | /api/v1/admin/knowledge-spaces/{id}/stats |
Knowledge-space usage stats (admin:knowledge_spaces) |
| GET/POST | /api/v1/admin/documents/overview, /bulk-reprocess, /bulk-delete |
Document ops (admin:documents) |
| GET/POST | /api/v1/admin/ingestion/overview, /jobs/{id}/cancel |
Ingestion ops (admin:ingestion) |
| GET | /api/v1/admin/audit/export |
Audit CSV/JSON export (admin:audit) |
| GET/POST | /api/v1/admin/usage/* |
Usage trends, token cost, pricing, export (admin:usage) |
| CRUD | /api/v1/admin/prompts, /activate, /deactivate, /rollback, /preview |
Prompt versioning (admin:prompts) |
| CRUD | /api/v1/admin/llm-providers, /{id}/test |
LLM configs with masked secrets (admin:llm) |
| CRUD | /api/v1/admin/feature-flags |
Feature flags with Redis cache (admin:settings) |
| GET | /api/v1/admin/ops/overview |
Ops readiness + queue/LLM summary (admin:ops) |
| CRUD/POST | /api/v1/admin/retention/policies, /retention/run |
Retention policies and runs (admin:retention) |
All endpoints above (except /health/* and /system/info) require
development identity headers and are subject to
scoped RBAC.
Example system info capabilities (implemented in this commit vs. still planned):
{
"identity_context": true,
"multi_tenancy": true,
"rbac": true,
"customers": true,
"projects": true,
"knowledge_spaces": true,
"audit_log": true,
"document_ingestion": true,
"document_parsing": true,
"document_chunking": true,
"document_embeddings": true,
"ingestion_workers": true,
"rag": true,
"chat": true,
"multilingual_answers": true,
"admin": true
}Permissions are namespaced resource:action strings; system roles are global (identical
across every organization) and cannot be created/modified through the API. Organizations can additionally define their
own custom roles with any subset of permissions via POST /api/v1/roles.
| System role | Summary |
|---|---|
platform_admin |
Bypasses every check; set directly on User.is_platform_admin, never assigned via the role API |
organization_admin |
Every permission below — full control of their organization |
project_manager |
Create/manage projects, knowledge spaces, and documents; read customers |
knowledge_manager |
Create/manage knowledge spaces and documents; read customers/projects |
developer |
Read-only: customers, projects, knowledge spaces; create/read/update documents (no delete) |
support_agent |
Read-only: customers, projects, knowledge spaces, documents |
viewer |
Read-only: customers, projects, knowledge spaces, documents |
| Permission | Meaning |
|---|---|
organization:read, organization:update, organization:manage_members |
Organization details and membership |
user:read, user:manage |
Users within a shared organization |
role:read, role:manage |
Custom roles and role assignments |
customer:create/read/update/archive |
Customers |
project:create/read/update/archive/manage_members |
Projects |
knowledge_space:create/read/update/archive/manage_members |
Knowledge spaces |
document:create/read/update/delete |
Documents |
rag:query |
Hybrid retrieval and grounded RAG answers |
chat:use |
Create/use conversations and send messages (granted to the same roles as rag:query) |
chat:manage |
Moderate any conversation and view chat analytics (organization_admin, knowledge_manager) |
admin:* |
Phase 4 governance (dashboard, users, organizations, roles, knowledge_spaces, documents, ingestion, audit, usage, prompts, llm, settings, ops, retention). Full set for organization_admin; knowledge managers get KS/documents/ingestion |
audit:read |
The audit trail |
Every user can always read/update their own profile (GET/PATCH /users/{their own id})
without holding user:read/user:manage.
Knowledge spaces have two visibility levels:
organization(default) — visible to anyone in the organization holdingknowledge_space:read. No explicit grant needed.restricted— requires an explicit grant: either a knowledge-space-scoped role assignment, or aKnowledgeSpaceMembershiprow. Holding org-wideknowledge_space:readis not sufficient — even the organization admin who created a restricted space gets404(not403) without an explicit grant, so a caller can never distinguish "exists but restricted" from "does not exist" (seetests/security/test_restricted_knowledge_space_access.py).platform_adminbypasses both rules.
# Seed a local dev tenant and capture the printed admin headers.
make bootstrap-dev
USER_ID="<admin uuid printed above>"
ORG_ID="<org uuid printed above>"
# System info (no auth required).
curl -s http://localhost:8000/api/v1/system/info | jq
# List organizations the admin is a member of.
curl -s http://localhost:8000/api/v1/organizations \
-H "X-ContextForge-User-ID: $USER_ID" \
-H "X-ContextForge-Organization-ID: $ORG_ID" | jq
# Create a customer as the organization admin.
curl -s -X POST http://localhost:8000/api/v1/customers \
-H "X-ContextForge-User-ID: $USER_ID" \
-H "X-ContextForge-Organization-ID: $ORG_ID" \
-H "Content-Type: application/json" \
-d '{"name": "Acme Corp", "code": "ACME"}' | jq
# Missing identity headers -> 401.
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8000/api/v1/customersDevelopment identity is an interim mechanism only. Planned follow-ups, in rough order:
- Real authentication (OIDC/SSO) replacing header-based identity, without changing
RequestContextor any application service's signature. - Session/token issuance and refresh flows.
- Per-organization identity provider configuration (enterprise SSO).
- Service-to-service / API-key authentication for automation clients.
- Removing development identity from non-production builds entirely once (1)–(2) ship.
See Planned roadmap below for the rest of the product roadmap.
/health/livealways checks process liveness only./health/readyprobes dependencies concurrently with timeouts.- Any mandatory dependency down → HTTP 503 and
"status": "not_ready". - Responses never include credentials or stack traces.
| Symptom | Likely cause | Fix |
|---|---|---|
| Ready returns 503 | Dependency not healthy | docker compose ps and inspect service logs |
| Migrations fail | Postgres not ready | Ensure postgres is healthy, rerun make migrate |
| MinIO check fails | Bucket missing | Ensure minio-init completed successfully |
| Docs missing in prod | Expected | Docs disabled when CONTEXTFORGE_APP_ENVIRONMENT=production |
- Do not commit
.envor secrets. - Containers run as non-root (
uid 10001). - CORS is off unless origins are explicitly configured.
- Development identity is not production authentication — it is unconditionally
disabled in
staging/production(see Development identity headers). Real authentication (OIDC/SSO) is tracked in the Auth roadmap and has not shipped yet — treat any non-production deployment of this API as an internal foundation only. - Authorization (RBAC + tenancy) is enforced server-side only; no client-supplied role/permission header is ever trusted.
- Audit metadata is sanitized to strip secret-like keys before persistence.
- English for code, comments, docs, logs, and commits
- UTC timestamps in the backend
- User-facing timezone conversion will be handled at presentation boundaries later
- No LangChain/LangGraph/LLM SDKs in this foundation commit
Multi-tenancy, scoped RBAC, and audit logging— done (development identity only; see Auth roadmap for real authentication)Document upload, parsing, chunking, embeddings, and ingestion workers— doneHybrid retrieval, reranking, and RAG answering— doneMultilingual chat experience (Turkish / English) with session memory— doneAdministration & governance— done (Phase 4)Production engineering (Docker/Helm/Terraform, observability, CI/CD, DR)— done (Phase 5)- Real authentication (OIDC/SSO) replacing development identity
- Deeper admin UX and org-level SSO configuration
MIT — see LICENSE.
Built by serencarikci.
Phase 4 adds organization administration under /api/v1/admin/* with dedicated
admin:* permissions. Organization admins receive the full set; knowledge managers
receive knowledge-space/document/ingestion admin permissions.
| Area | Endpoints (prefix /api/v1/admin) |
|---|---|
| Dashboard | GET /dashboard |
| Users | GET /users, activate/deactivate |
| Org settings | `GET |
| Roles | PUT /roles/{id}/permissions, DELETE /roles/{id} |
| Knowledge spaces | GET /knowledge-spaces/{id}/stats |
| Documents | GET /documents/overview, bulk reprocess/delete |
| Ingestion | GET /ingestion/overview, cancel pending jobs |
| Audit | `GET /audit/export?format=json |
| Usage / tokens | overview, trends, tokens, pricing, export |
| Prompts | CRUD + activate/deactivate/preview |
| LLM providers | CRUD + connectivity test (API keys masked) |
| Feature flags | CRUD + resolved map (cached) |
| Ops | GET /ops/overview |
| Retention | policies CRUD + POST /retention/run |
flowchart TD
Admin[Organization Admin] --> API["/api/v1/admin"]
API --> Services[Admin services]
Services --> DB[(Postgres admin + tenant tables)]
Services --> Redis[(Feature-flag cache)]
Worker[contextforge-retention-worker] --> Retention[RetentionCleanupService]
Retention --> DB
CONTEXTFORGE_ADMIN_RETENTION_ENABLED=true
CONTEXTFORGE_ADMIN_RETENTION_BATCH_SIZE=500
CONTEXTFORGE_ADMIN_RETENTION_DEFAULT_DAYS=365
CONTEXTFORGE_ADMIN_RETENTION_WORKER_INTERVAL_SECONDS=3600.0
CONTEXTFORGE_ADMIN_CACHE_TTL_SECONDS=30
CONTEXTFORGE_ADMIN_TOKEN_USAGE_ROLLUP_ENABLED=true
CONTEXTFORGE_ADMIN_TOKEN_PRICING_CURRENCY=USD
CONTEXTFORGE_ADMIN_LLM_TEST_TIMEOUT_SECONDS=5.0Phase 5 hardens operations without rewriting Phase 1–4 product features. Version 0.5.0 ships security headers, rate limiting, Prometheus metrics, retention worker in Compose, Helm/Terraform deploy stubs, CD, backups, load tests, and runbooks.
flowchart TB
Dev[Developer / CI] --> GHCR[GHCR image]
GHCR --> Helm[Helm chart]
Helm --> API[API Deployment]
Helm --> Ingest[Ingestion Worker]
Helm --> Retain[Retention Worker]
API --> Prom[Prometheus /metrics]
Prom --> Graf[Grafana dashboards]
API --> PG[(Postgres)]
API --> Redis[(Redis)]
Cron[Backup CronJobs] --> PG
Cron --> MinIO[(Object storage)]
Cron --> Qdrant[(Qdrant)]
| Artifact | Purpose |
|---|---|
Dockerfile |
Multi-stage, non-root, OCI labels, STOPSIGNAL, worker-friendly healthcheck skip via CONTEXTFORGE_SKIP_HEALTHCHECK |
docker-compose.yml |
Dev stack + retention-worker + optional --profile obs (Prometheus/Grafana/Loki) |
docker-compose.prod.yml |
Resource limits, env_file: .env, restart policies, no unnecessary host binds |
docker-compose.obs.yml |
Observability overlay (same profile) |
docker compose config
docker compose -f docker-compose.yml -f docker-compose.prod.yml config
docker compose --profile obs up -d
make compose-prod-config- Security headers:
X-Content-Type-Options,X-Frame-Options,Referrer-Policy,Permissions-Policy; HSTS when notlocal/test/development - Rate limit: sliding window for
/api/v1(memoryorredis), health paths excluded - Metrics:
GET /metrics(Prometheus text) with request counters, latency histogram, dependency-up gauges
CONTEXTFORGE_RATE_LIMIT_ENABLED=true
CONTEXTFORGE_RATE_LIMIT_REQUESTS=120
CONTEXTFORGE_RATE_LIMIT_WINDOW_SECONDS=60
CONTEXTFORGE_RATE_LIMIT_BACKEND=memory
CONTEXTFORGE_OBSERVABILITY_METRICS_ENABLED=true
CONTEXTFORGE_OBSERVABILITY_METRICS_PATH=/metricsChart: deploy/helm/contextforge/ with values for dev/staging/prod. Templates cover
namespace, SA, ConfigMap, ExternalSecret stub, API/ingestion/retention Deployments,
migrate Job, Service, Ingress, HPA, PDB, NetworkPolicy, optional ServiceMonitor,
and backup CronJob.
make helm-lint
# or
./scripts/validate-helm.shProvider-neutral stubs under deploy/terraform/modules/ (networking, kubernetes,
postgres, redis, object_storage, dns) composed by
deploy/terraform/environments/{staging,production}.
make terraform-validate
# or
./scripts/validate-terraform.sh
cd deploy/terraform/environments/staging
terraform init -backend=false
terraform validateWire real cloud providers in forks; modules intentionally avoid vendor lock-in.
| Workflow | Trigger | What it does |
|---|---|---|
.github/workflows/ci.yml |
push/PR | lint, mypy, unit/arch, migration SQL check, helm/terraform, secret scan, SBOM soft-fail, docker build |
.github/workflows/cd.yml |
v* tag / dispatch |
build/push GHCR, staging deploy render + smoke, production environment (approval) |
.github/workflows/release.yml |
v* tag |
changelog stub + GitHub Release |
Default workflow permissions are least-privilege (contents: read unless release needs write).
- Never commit
.envor live credentials - Example ExternalSecret:
deploy/secrets/external-secrets.example.yaml - Helm references
existingSecret/ External Secrets only (no secret values in values files) - Rotation: update secret manager → refresh ExternalSecret → restart API/workers — see
ops/runbooks/secret-compromise.md
Configs live under deploy/observability/. Scrape api:8000/metrics, alert rules include
severity, description, and runbook_url labels. Grafana dashboard:
contextforge-overview.json.
| SLO (initial goals — not claimed achieved) | Target |
|---|---|
| Availability (monthly) | 99.9% |
| API readiness success | 99.5% |
| HTTP request success (non-5xx) | 99.0% |
| Latency p95 (read paths) | < 1.0s |
| Latency p95 (RAG/chat) | < 5.0s |
| Backup success rate | 99% |
Scripts: scripts/backup/backup_postgres.sh, backup_minio.sh, backup_qdrant.sh,
restore_postgres.sh, verify_backup.sh. CronJob examples under
deploy/k8s/cronjobs/ and Helm backupCronJob.
| DR goal (documented targets) | Value |
|---|---|
| RPO | ≤ 24h (daily backups) |
| RTO | ≤ 4h (restore + verify + cutover) |
k6 scenarios (document thresholds in scripts; requires k6 installed):
make load-test-smoke
k6 run perf/k6/load_chat.js
k6 run perf/k6/load_rag.jsConcise runbooks under ops/runbooks/: deploy-failure, api-outage, postgres-outage,
redis-outage, queue-backlog, llm-outage, rollback, backup-restore, secret-compromise.
Production checklist: ops/production-readiness-checklist.md.
make validate-infra
make helm-lint
make terraform-validate
make backup-postgres
make load-test-smoke
make compose-prod-config

























