diff --git a/CLAUDE.md b/CLAUDE.md index 4ca14a4..e53f67d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -56,6 +56,8 @@ LLM Provider Adapters (OpenAI, Anthropic, Azure, Mistral, Ollama) **Frontend:** React 18 + TypeScript + Vite, shadcn/ui, recharts. Routes protected via OIDC (Keycloak); `web/src/auth/` manages the auth flow. API clients live in `web/src/api/`. +**Documentation site** (`http://localhost:3000/docs`): public, no auth required. Root: `web/src/pages/docs/` — sections: getting-started, installation, api-reference (8 endpoints), guides (6), deployment (3), security (2), changelog. Layout components: `DocLayout.tsx` (sidebar + content + TOC), `DocSidebar.tsx` (with search), `DocBreadcrumbs.tsx`, `DocPagination.tsx`. Shared components: `components/CodeBlock.tsx`, `Callout.tsx`, `ApiEndpoint.tsx`, `ParamTable.tsx`, `TableOfContents.tsx`. Nav structure: `web/src/pages/docs/nav.ts`. Uses `@tailwindcss/typography` (added as devDependency) for prose rendering. + ## Repository Structure ``` @@ -69,6 +71,7 @@ proto/pii/v1/ # gRPC .proto definitions migrations/ # golang-migrate SQL files (up/down pairs) clickhouse/ # ClickHouse DDL applied at startup via ApplyDDL() web/ # React frontend (Vite, src/pages, src/components, src/api) + src/pages/docs/ # Public documentation site (no auth); nav.ts defines sidebar structure test/ # Integration tests (test/integration/, //go:build integration) + k6 load tests (test/k6/) deploy/ # Helm, Kubernetes manifests, Terraform (EKS), Prometheus/Grafana, alertmanager clickhouse/ # ClickHouse config overrides for Docker (e.g. listen-ipv4.xml — forces IPv4) diff --git a/web/package-lock.json b/web/package-lock.json index 75bdea7..5e63224 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -32,6 +32,7 @@ "zod": "^3.23.8" }, "devDependencies": { + "@tailwindcss/typography": "^0.5.19", "@types/node": "^22.5.5", "@types/react": "^18.3.5", "@types/react-dom": "^18.3.0", @@ -2034,6 +2035,33 @@ "win32" ] }, + "node_modules/@tailwindcss/typography": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz", + "integrity": "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "6.0.10" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" + } + }, + "node_modules/@tailwindcss/typography/node_modules/postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/@tanstack/query-core": { "version": "5.90.20", "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.20.tgz", diff --git a/web/package.json b/web/package.json index 6ccb7ad..708979d 100644 --- a/web/package.json +++ b/web/package.json @@ -34,6 +34,7 @@ "zod": "^3.23.8" }, "devDependencies": { + "@tailwindcss/typography": "^0.5.19", "@types/node": "^22.5.5", "@types/react": "^18.3.5", "@types/react-dom": "^18.3.0", diff --git a/web/src/pages/LandingPage.tsx b/web/src/pages/LandingPage.tsx index d4f0fd2..5c81bed 100644 --- a/web/src/pages/LandingPage.tsx +++ b/web/src/pages/LandingPage.tsx @@ -257,7 +257,7 @@ export function LandingPage() { Sécurité Pour qui Playground - Docs + Docs Se connecter au dashboard - + Documentation @@ -741,15 +741,9 @@ export function LandingPage() { Veylant IA
- {[ - { label: "Documentation", href: "http://localhost:8090/docs" }, - { label: "Playground", href: "http://localhost:8090/playground" }, - { label: "Contact", href: "mailto:demo@veylant.io" }, - ].map((l) => ( - - {l.label} - - ))} + Documentation + Playground + Contact
© 2026 Veylant. Conçu pour l'entreprise européenne. diff --git a/web/src/pages/docs/ChangelogPage.tsx b/web/src/pages/docs/ChangelogPage.tsx new file mode 100644 index 0000000..9f8db25 --- /dev/null +++ b/web/src/pages/docs/ChangelogPage.tsx @@ -0,0 +1,151 @@ +import { Callout } from "./components/Callout"; +import { Link } from "react-router-dom"; + +const v100Changes = { + features: [ + "AI proxy with OpenAI-compatible API (/v1/chat/completions)", + "3-layer PII detection: regex + spaCy NER + LLM validation", + "PII pseudonymization with AES-256-GCM Redis mappings", + "Intelligent routing engine (PostgreSQL JSONB rules, in-memory cache, priority ASC)", + "RBAC: admin, manager, user, auditor roles with model restrictions", + "Streaming SSE support (PII anonymization on request, not response)", + "Circuit breaker per provider (threshold=5, TTL=60s) with fallback chain", + "Token-bucket rate limiting (per-tenant + per-user, DB overrides)", + "ClickHouse append-only audit logs with async batch writer", + "GDPR Article 30 processing registry with PDF/JSON export", + "EU AI Act risk classification questionnaire (forbidden/high/limited/minimal)", + "DPIA template generation", + "GDPR Art. 15 (Right of Access) and Art. 17 (Right to Erasure) endpoints", + "Feature flags (PostgreSQL + in-memory fallback)", + "React 18 dashboard with 11 pages (French UI)", + "Public AI playground at /playground (IP rate-limited 20/min)", + "Swagger UI at /docs", + "Provider adapters: OpenAI, Anthropic, Azure, Mistral, Ollama", + "Prometheus metrics + Grafana dashboards (overview + SLO)", + "7 Prometheus alerts with Alertmanager (PagerDuty + Slack)", + "HPA autoscaling 3→15 replicas (CPU 70% + memory 80%)", + "Blue/green deployment with Istio VirtualService (<5s rollback)", + "Terraform EKS v1.31, 3-AZ, eu-west-3", + "PostgreSQL backup CronJob (daily 02:00 UTC, S3, 7-day retention)", + "k6 load tests: smoke, load, stress, soak scenarios", + "HashiCorp Vault integration for API key rotation (90-day cycle)", + "AES-256-GCM prompt encryption", + "Production SLO dashboard (99.5% availability)", + "6 operational runbooks", + "Admin guide + integration guide + onboarding scripts", + "Pentest scope + remediation report (0 Critical, 0 High)", + "Commercial materials: one-pager, pitch deck, battle card", + "CHANGELOG.md with full v1.0.0 history", + "GitHub Actions release pipeline (multi-arch Docker + Helm OCI + GitHub Release)", + ], + bugfixes: [ + "Rate limit 429 responses now include Retry-After: 1 header (RFC 6585)", + "CSP header updated to allow dashboard iframe embeds", + "CORS allowed origins now configurable (server.allowed_origins) instead of wildcard", + "Routing rule cache race condition on concurrent updates fixed", + ], +}; + +const v110Roadmap = [ + { priority: "Must-have", item: "Layer 3 LLM validation for PII (ambiguous cases)" }, + { priority: "Must-have", item: "Per-routing-rule PII control (enable/disable per rule)" }, + { priority: "Must-have", item: "Native OpenAI SDK (Go) for seamless migration" }, + { priority: "Should-have", item: "ML anomaly detection on prompt patterns" }, + { priority: "Should-have", item: "Shadow AI discovery (network traffic analysis)" }, + { priority: "Should-have", item: "Physical multi-tenant isolation (separate DB schemas)" }, + { priority: "Should-have", item: "SIEM integrations (Splunk, Elastic)" }, + { priority: "Could-have", item: "Custom model adapters via plugin API" }, + { priority: "Could-have", item: "Prompt caching (provider-level + in-proxy)" }, + { priority: "Could-have", item: "Cost budget alerts via email/Slack (direct)" }, +]; + +export function ChangelogPage() { + return ( +
+

Changelog

+

+ All notable changes to Veylant IA are documented here. Versioning follows{" "} + + Semantic Versioning + + . +

+ +

v1.0.0 — February 25, 2026

+ + First production release. Pentest passed (0 Critical, 0 High). 2 pilot clients migrated. + + +

Features

+ + +

Bug Fixes

+ + +

v1.1.0 — Planned (Q2 2026)

+

+ Priorities sourced from 2 pilot client sessions (MoSCoW method). See{" "} + docs/feedback-backlog.md for the full backlog. +

+ +
+ + + + + + + + + {v110Roadmap.map((item) => ( + + + + + ))} + +
PriorityFeature
+ + {item.priority} + + {item.item}
+
+ +

Migration & Compatibility

+

+ V1.0.0 has no breaking changes from the beta releases used by pilot clients. The{" "} + /v1/chat/completions API is fully backward-compatible with the OpenAI API + format. Any client using the OpenAI SDK will continue to work without changes. +

+

+ See the Installation guide for upgrade + instructions from beta. +

+
+ ); +} diff --git a/web/src/pages/docs/DocBreadcrumbs.tsx b/web/src/pages/docs/DocBreadcrumbs.tsx new file mode 100644 index 0000000..485b515 --- /dev/null +++ b/web/src/pages/docs/DocBreadcrumbs.tsx @@ -0,0 +1,28 @@ +import { Link, useLocation } from "react-router-dom"; +import { ChevronRight } from "lucide-react"; +import { NAV_SECTIONS } from "./nav"; + +export function DocBreadcrumbs() { + const { pathname } = useLocation(); + + const section = NAV_SECTIONS.find((s) => s.items.some((i) => i.path === pathname)); + const item = section?.items.find((i) => i.path === pathname); + + if (!section || !item) return null; + + return ( + + ); +} diff --git a/web/src/pages/docs/DocLayout.tsx b/web/src/pages/docs/DocLayout.tsx new file mode 100644 index 0000000..2fcbc62 --- /dev/null +++ b/web/src/pages/docs/DocLayout.tsx @@ -0,0 +1,51 @@ +import { Outlet, useLocation } from "react-router-dom"; +import { DocSidebar } from "./DocSidebar"; +import { DocBreadcrumbs } from "./DocBreadcrumbs"; +import { DocPagination } from "./DocPagination"; +import { TableOfContents } from "./components/TableOfContents"; + +export function DocLayout() { + const { pathname } = useLocation(); + const isHome = pathname === "/docs"; + + return ( +
+ {/* Left sidebar */} + + + {/* Main content */} +
+ {/* Article */} +
+
+ {!isHome && } +
+ +
+ {!isHome && } +
+
+ + {/* Right TOC (hidden on home page and small screens) */} + {!isHome && ( +
+ +
+ )} +
+
+ ); +} diff --git a/web/src/pages/docs/DocPagination.tsx b/web/src/pages/docs/DocPagination.tsx new file mode 100644 index 0000000..c95e9ca --- /dev/null +++ b/web/src/pages/docs/DocPagination.tsx @@ -0,0 +1,43 @@ +import { Link, useLocation } from "react-router-dom"; +import { ChevronLeft, ChevronRight } from "lucide-react"; +import { getPrevNext } from "./nav"; + +export function DocPagination() { + const { pathname } = useLocation(); + const { prev, next } = getPrevNext(pathname); + + if (!prev && !next) return null; + + return ( +
+ {prev ? ( + + +
+
Previous
+
{prev.title}
+
+ + ) : ( +
+ )} + {next ? ( + +
+
Next
+
{next.title}
+
+ + + ) : ( +
+ )} +
+ ); +} diff --git a/web/src/pages/docs/DocSidebar.tsx b/web/src/pages/docs/DocSidebar.tsx new file mode 100644 index 0000000..99c2b7f --- /dev/null +++ b/web/src/pages/docs/DocSidebar.tsx @@ -0,0 +1,139 @@ +import { useState } from "react"; +import { NavLink, Link } from "react-router-dom"; +import { Search, Shield, X } from "lucide-react"; +import { cn } from "@/lib/utils"; +import { NAV_SECTIONS, ALL_NAV_ITEMS } from "./nav"; + +export function DocSidebar() { + const [query, setQuery] = useState(""); + + const filtered = + query.trim().length > 0 + ? ALL_NAV_ITEMS.filter( + (i) => + i.title.toLowerCase().includes(query.toLowerCase()) || + i.section.toLowerCase().includes(query.toLowerCase()) + ) + : null; + + return ( + + ); +} diff --git a/web/src/pages/docs/DocsHomePage.tsx b/web/src/pages/docs/DocsHomePage.tsx new file mode 100644 index 0000000..0d9eca7 --- /dev/null +++ b/web/src/pages/docs/DocsHomePage.tsx @@ -0,0 +1,203 @@ +import { Link } from "react-router-dom"; +import { + Rocket, + Package, + Plug, + Shield, + GitBranch, + Scale, + Ship, + Lock, + ArrowRight, + Sparkles, +} from "lucide-react"; +import { Callout } from "./components/Callout"; + +const cards = [ + { + icon: Rocket, + title: "Quick Start", + description: "Get Veylant IA running in 5 minutes with Docker Compose.", + href: "/docs/getting-started/quick-start", + color: "bg-blue-50 dark:bg-blue-950/30 text-blue-600 dark:text-blue-400", + }, + { + icon: Package, + title: "Installation", + description: "Configure services, providers, and environment variables.", + href: "/docs/installation/docker", + color: "bg-purple-50 dark:bg-purple-950/30 text-purple-600 dark:text-purple-400", + }, + { + icon: Plug, + title: "API Reference", + description: "Full reference for all REST endpoints, parameters, and responses.", + href: "/docs/api/chat-completions", + color: "bg-green-50 dark:bg-green-950/30 text-green-600 dark:text-green-400", + }, + { + icon: Shield, + title: "PII & Privacy", + description: "3-layer PII detection, pseudonymization, and Redis-backed mappings.", + href: "/docs/guides/pii", + color: "bg-red-50 dark:bg-red-950/30 text-red-600 dark:text-red-400", + }, + { + icon: GitBranch, + title: "Routing Rules", + description: "Route AI requests by role, department, model, or sensitivity.", + href: "/docs/guides/routing", + color: "bg-amber-50 dark:bg-amber-950/30 text-amber-600 dark:text-amber-400", + }, + { + icon: Scale, + title: "Compliance", + description: "GDPR Article 30 registry, AI Act risk classification, and DPIA.", + href: "/docs/guides/compliance", + color: "bg-teal-50 dark:bg-teal-950/30 text-teal-600 dark:text-teal-400", + }, + { + icon: Ship, + title: "Deployment", + description: "Docker, Kubernetes Helm chart, and blue/green with Istio.", + href: "/docs/deployment/kubernetes", + color: "bg-indigo-50 dark:bg-indigo-950/30 text-indigo-600 dark:text-indigo-400", + }, + { + icon: Lock, + title: "Security", + description: "Zero Trust, mTLS, AES-256-GCM encryption, and audit trails.", + href: "/docs/security/model", + color: "bg-slate-50 dark:bg-slate-950/30 text-slate-600 dark:text-slate-400", + }, +]; + +export function DocsHomePage() { + return ( +
+ {/* Hero */} +
+
+ + v1.0.0 — Generally Available + +
+

+ Veylant IA Documentation +

+

+ Everything you need to build, configure, and operate your enterprise AI governance + platform. Prevent Shadow AI, enforce PII anonymization, ensure GDPR compliance, and + control costs across all LLM usage. +

+
+ + {/* What's new */} + + Pentest passed (0 Critical, 0 High), 2 pilot clients migrated, blue/green deployment with + Istio, HPA autoscaling (3→15 replicas), 7 Prometheus alerts, SLO dashboard (99.5%), and 6 + operational runbooks.{" "} + + Read the full changelog → + + + + {/* Cards grid */} +
+ {cards.map((card) => ( + +
+ +
+
+
+

{card.title}

+ +
+

+ {card.description} +

+
+ + ))} +
+ + {/* Quick links */} +
+
+
+ + Popular pages +
+
    + {[ + { title: "Chat Completions API", href: "/docs/api/chat-completions" }, + { title: "Routing Rules Engine", href: "/docs/guides/routing" }, + { title: "RBAC & Permissions", href: "/docs/guides/rbac" }, + { title: "GDPR Compliance", href: "/docs/guides/compliance" }, + ].map((l) => ( +
  • + + + {l.title} + +
  • + ))} +
+
+ +
+
+ + AI Providers +
+
    + {[ + "OpenAI (gpt-4o, gpt-4-turbo)", + "Anthropic (claude-3-5-sonnet)", + "Azure OpenAI", + "Mistral AI", + "Ollama (self-hosted)", + ].map((p) => ( +
  • +
    + {p} +
  • + ))} +
+
+ +
+
+ + Compatibility +
+

+ OpenAI-compatible API — drop-in replacement: +

+
    + {[ + "openai Python SDK", + "openai Node.js SDK", + "LangChain", + "LlamaIndex", + "Any HTTP client", + ].map((c) => ( +
  • +
    + {c} +
  • + ))} +
+
+
+
+ ); +} diff --git a/web/src/pages/docs/api-reference/AdminCompliancePage.tsx b/web/src/pages/docs/api-reference/AdminCompliancePage.tsx new file mode 100644 index 0000000..d5225c5 --- /dev/null +++ b/web/src/pages/docs/api-reference/AdminCompliancePage.tsx @@ -0,0 +1,143 @@ +import { CodeBlock } from "../components/CodeBlock"; +import { Callout } from "../components/Callout"; +import { ApiEndpoint } from "../components/ApiEndpoint"; +import { ParamTable } from "../components/ParamTable"; + +export function AdminCompliancePage() { + return ( +
+

Admin — Compliance

+

+ GDPR Article 30 processing registry, EU AI Act risk classification, and data subject + rights (access and erasure). +

+ + + Compliance endpoints require admin role. Auditors can read but not modify. + + +

GDPR Article 30 — Processing Registry

+ + + + + + + + + + +

EU AI Act Classification

+ + + + +

Risk level mapping:

+
+ + + + + + + + + + {[ + { score: "5", level: "Forbidden", desc: "System must not be deployed. Example: social scoring, real-time biometric surveillance in public spaces.", color: "text-red-600" }, + { score: "3–4", level: "High", desc: "Strict conformity assessment required before deployment. DPIA mandatory.", color: "text-orange-600" }, + { score: "1–2", level: "Limited", desc: "Transparency obligations: users must be informed they interact with AI.", color: "text-amber-600" }, + { score: "0", level: "Minimal", desc: "Minimal risk. Voluntary code of conduct recommended.", color: "text-green-600" }, + ].map((row) => ( + + + + + + ))} + +
ScoreLevelDescription
{row.score}{row.level}{row.desc}
+
+ +

GDPR Subject Rights

+ +

Article 15 — Right of Access

+ + + +

Article 17 — Right to Erasure

+ + + + ClickHouse audit logs cannot be deleted. The erasure endpoint scrubs PII from prompt + content and pseudonymizes user identifiers, but the request metadata (token counts, cost, + timestamps) is retained for compliance reporting. + +
+ ); +} diff --git a/web/src/pages/docs/api-reference/AdminFlagsPage.tsx b/web/src/pages/docs/api-reference/AdminFlagsPage.tsx new file mode 100644 index 0000000..a907071 --- /dev/null +++ b/web/src/pages/docs/api-reference/AdminFlagsPage.tsx @@ -0,0 +1,98 @@ +import { CodeBlock } from "../components/CodeBlock"; +import { Callout } from "../components/Callout"; +import { ApiEndpoint } from "../components/ApiEndpoint"; + +export function AdminFlagsPage() { + return ( +
+

Admin — Feature Flags

+

+ Feature flags control behavior at runtime without redeployment. Flags are stored in + PostgreSQL with an in-memory cache (updated every 30 seconds). If PostgreSQL is + unavailable, the in-memory defaults are used. +

+ + + Feature flag management requires the admin role. + + +

List All Flags

+ + + +

Get a Flag

+ + +

Set a Flag

+ + + +

Delete a Flag

+ + +

Default Flag Values

+
+ + + + + + + + + + {[ + { flag: "pii_detection", def: "true", effect: "Prompts forwarded without PII scanning" }, + { flag: "pii_pseudonymization", def: "true", effect: "PII detected but not stored in Redis" }, + { flag: "audit_logging", def: "true", effect: "Requests not written to ClickHouse" }, + { flag: "playground", def: "true", effect: "POST /playground/analyze returns 404" }, + { flag: "streaming", def: "true", effect: "SSE requests return 400" }, + { flag: "cost_tracking", def: "true", effect: "Token cost not computed or stored" }, + { flag: "circuit_breaker", def: "true", effect: "Failures not counted, no fallback" }, + ].map((row) => ( + + + + + + ))} + +
FlagDefaultEffect when disabled
{row.flag} + + {row.def} + + {row.effect}
+
+ + + Disabling audit_logging or pii_detection in production may + violate GDPR obligations and internal SLA requirements. Always coordinate with your DPO + before making changes. + +
+ ); +} diff --git a/web/src/pages/docs/api-reference/AdminLogsPage.tsx b/web/src/pages/docs/api-reference/AdminLogsPage.tsx new file mode 100644 index 0000000..0849ced --- /dev/null +++ b/web/src/pages/docs/api-reference/AdminLogsPage.tsx @@ -0,0 +1,133 @@ +import { CodeBlock } from "../components/CodeBlock"; +import { Callout } from "../components/Callout"; +import { ApiEndpoint } from "../components/ApiEndpoint"; +import { ParamTable } from "../components/ParamTable"; + +export function AdminLogsPage() { + return ( +
+

Admin — Audit Logs & Costs

+

+ Query the immutable audit trail and cost breakdown for AI requests. All data is stored in + ClickHouse (append-only — no DELETE operations). +

+ +

Audit Logs

+ + + + + + + + All accesses to audit logs are themselves logged. This satisfies the "audit-of-the-audit" + requirement for sensitive compliance use cases. + + +

Cost Breakdown

+ + + + + + +

Rate Limit Overrides

+ + + + +
+ ); +} diff --git a/web/src/pages/docs/api-reference/AdminPoliciesPage.tsx b/web/src/pages/docs/api-reference/AdminPoliciesPage.tsx new file mode 100644 index 0000000..5820ca1 --- /dev/null +++ b/web/src/pages/docs/api-reference/AdminPoliciesPage.tsx @@ -0,0 +1,124 @@ +import { CodeBlock } from "../components/CodeBlock"; +import { Callout } from "../components/Callout"; +import { ApiEndpoint } from "../components/ApiEndpoint"; +import { ParamTable } from "../components/ParamTable"; + +export function AdminPoliciesPage() { + return ( +
+

Admin — Routing Policies

+

+ Routing policies (rules) define how AI requests are dispatched to providers. Rules are + evaluated in ascending priority order; the first matching rule wins. +

+ + + All /v1/admin/policies endpoints require the admin or{" "} + manager role. + + +

List Policies

+ + + +

Create Policy

+ + + +

Condition Fields

+
+ + + + + + + + + + {[ + { field: "user.role", ops: "eq, neq, in, nin", ex: '"admin"' }, + { field: "user.department", ops: "eq, neq, in, nin, contains", ex: '"legal"' }, + { field: "request.model", ops: "eq, neq, in, nin", ex: '"gpt-4o"' }, + { field: "request.sensitivity", ops: "eq, neq, gte, lte", ex: '"high"' }, + { field: "request.use_case", ops: "eq, neq, contains, matches", ex: '"code_review"' }, + { field: "request.token_estimate", ops: "gte, lte", ex: '"4000"' }, + ].map((row) => ( + + + + + + ))} + +
FieldOperatorsValue example
{row.field}{row.ops}{row.ex}
+
+ + + +

Update Policy

+ + +

Delete Policy

+ + +

Seed from Template

+ +

Available templates:

+
    +
  • default — Single catch-all rule routing to OpenAI
  • +
  • rbac — Rules per RBAC role with model restrictions
  • +
  • department — Rules per department (legal, hr, engineering, finance)
  • +
  • cost-optimized — Routes to cheaper models for simple queries
  • +
+
+ ); +} diff --git a/web/src/pages/docs/api-reference/AdminUsersPage.tsx b/web/src/pages/docs/api-reference/AdminUsersPage.tsx new file mode 100644 index 0000000..87f457d --- /dev/null +++ b/web/src/pages/docs/api-reference/AdminUsersPage.tsx @@ -0,0 +1,91 @@ +import { CodeBlock } from "../components/CodeBlock"; +import { Callout } from "../components/Callout"; +import { ApiEndpoint } from "../components/ApiEndpoint"; +import { ParamTable } from "../components/ParamTable"; + +export function AdminUsersPage() { + return ( +
+

Admin — Users

+

+ Manage users within a tenant. Users are synchronized from Keycloak but can have additional + metadata (department, cost overrides) managed through the admin API. +

+ + + Requires admin role. Managers can read users but cannot create or delete. + + +

List Users

+ + + +

Create User

+ + + +

Get User

+ + +

Update User

+ + +

RBAC Role Reference

+
+ + + + + + + + + + + + {[ + { role: "admin", infer: "Yes", models: "All", admin: "Full", audit: "Read" }, + { role: "manager", infer: "Yes", models: "All", admin: "Read-Write policies/users", audit: "Read" }, + { role: "user", infer: "Yes", models: "rbac.user_allowed_models only", admin: "None", audit: "None" }, + { role: "auditor", infer: "No", models: "—", admin: "None", audit: "Full read + export" }, + ].map((row) => ( + + + + + + + + ))} + +
RoleInferenceModelsAdmin APIAudit/Compliance
{row.role}{row.infer}{row.models}{row.admin}{row.audit}
+
+
+ ); +} diff --git a/web/src/pages/docs/api-reference/AuthenticationPage.tsx b/web/src/pages/docs/api-reference/AuthenticationPage.tsx new file mode 100644 index 0000000..6836d3b --- /dev/null +++ b/web/src/pages/docs/api-reference/AuthenticationPage.tsx @@ -0,0 +1,133 @@ +import { CodeBlock } from "../components/CodeBlock"; +import { Callout } from "../components/Callout"; + +export function AuthenticationPage() { + return ( +
+

Authentication

+

+ All /v1/* endpoints require a Bearer JWT in the{" "} + Authorization header. Veylant IA validates the token against Keycloak (OIDC) + or uses a mock verifier in development mode. +

+ +

Bearer Token

+ " \\ + -H "Content-Type: application/json" \\ + -d '{"model": "gpt-4o", "messages": [{"role": "user", "content": "Hi"}]}'`} + /> + +

Development Mode

+ + When server.env=development and Keycloak is unreachable, the proxy uses a{" "} + MockVerifier. Any non-empty Bearer token is accepted. The authenticated user + is injected as admin@veylant.dev with admin role and tenant ID{" "} + dev-tenant. + + + +

Production: Keycloak OIDC Flow

+

In production, clients obtain a token via the standard OIDC Authorization Code flow:

+
    +
  1. Redirect user to Keycloak login page
  2. +
  3. User authenticates; Keycloak redirects back with an authorization code
  4. +
  5. Exchange code for tokens at the token endpoint
  6. +
  7. Use the access_token as the Bearer token
  8. +
+ + +

JWT Claims

+

The proxy extracts the following claims from the JWT:

+
+ + + + + + + + + + {[ + { claim: "sub", source: "Standard JWT", desc: "User ID (UUID)" }, + { claim: "email", source: "Standard JWT", desc: "User email" }, + { claim: "realm_access.roles", source: "Keycloak extension", desc: "RBAC roles: admin, manager, user, auditor" }, + { claim: "veylant_tenant_id", source: "Keycloak mapper", desc: "Tenant UUID" }, + { claim: "department", source: "Keycloak user attribute", desc: "Department name for routing rules" }, + ].map((row) => ( + + + + + + ))} + +
ClaimSourceDescription
{row.claim}{row.source}{row.desc}
+
+ +

Pre-configured Test Users

+

The Keycloak realm export includes these users for testing:

+ + +

Auth Error Responses

+

Authentication errors always return OpenAI-format JSON:

+ +
+ ); +} diff --git a/web/src/pages/docs/api-reference/ChatCompletionsPage.tsx b/web/src/pages/docs/api-reference/ChatCompletionsPage.tsx new file mode 100644 index 0000000..f100505 --- /dev/null +++ b/web/src/pages/docs/api-reference/ChatCompletionsPage.tsx @@ -0,0 +1,194 @@ +import { CodeBlock } from "../components/CodeBlock"; +import { Callout } from "../components/Callout"; +import { ApiEndpoint } from "../components/ApiEndpoint"; +import { ParamTable } from "../components/ParamTable"; + +export function ChatCompletionsPage() { + return ( +
+

Chat Completions

+

+ The primary inference endpoint. Fully compatible with the OpenAI Chat Completions API — + switch your base_url to Veylant IA and all existing SDK calls work unchanged. +

+ + + + + This endpoint is a superset of the OpenAI Chat Completions API. All standard OpenAI + parameters are supported and forwarded to the upstream provider. Veylant IA adds governance + on top without changing the request/response schema. + + +

Request Body

+ + +

Example Request

+ + +

PII Anonymization in Action

+

+ Before the request is forwarded to the LLM, the PII service scans all message content. + Detected entities are anonymized in the prompt. The audit log records what was found. +

+ + +

Response

+

Responses are identical to the OpenAI API format:

+ + +

Streaming (SSE)

+

+ Set "stream": true to receive chunks via Server-Sent Events. PII + anonymization applies to the request before it's sent upstream — not to + the streamed response. This keeps streaming latency minimal. +

+ + + + +

Error Responses

+ + +

How Routing Affects the Model

+

+ The routing engine evaluates rules against the request context. A matched rule can override + the requested model or select a different provider. The model in the response + reflects the model actually used (which may differ from what was requested). +

+ + Use the GET /v1/admin/logs endpoint to see model_requested vs{" "} + model_used in the audit trail. + +
+ ); +} diff --git a/web/src/pages/docs/api-reference/PiiAnalysisPage.tsx b/web/src/pages/docs/api-reference/PiiAnalysisPage.tsx new file mode 100644 index 0000000..2e178c0 --- /dev/null +++ b/web/src/pages/docs/api-reference/PiiAnalysisPage.tsx @@ -0,0 +1,136 @@ +import { CodeBlock } from "../components/CodeBlock"; +import { Callout } from "../components/Callout"; +import { ApiEndpoint } from "../components/ApiEndpoint"; +import { ParamTable } from "../components/ParamTable"; + +export function PiiAnalysisPage() { + return ( +
+

PII Analysis

+

+ Analyze text for PII on demand. This endpoint exposes the same 3-layer detection pipeline + used internally for prompt anonymization — useful for testing policies, building pre-flight + checks, or auditing content. +

+ + + +

Request Body

+ + +

Example

+ + +

Response

+ + +

Supported Entity Types

+
+ + + + + + + + + + {[ + { type: "EMAIL_ADDRESS", layer: "regex", ex: "user@example.com" }, + { type: "PHONE_NUMBER", layer: "regex", ex: "+33 6 12 34 56 78, 06.12.34.56.78" }, + { type: "IBAN_CODE", layer: "regex", ex: "FR76 3000 6000 0112 3456 7890 189" }, + { type: "CREDIT_CARD", layer: "regex", ex: "4532 1234 5678 9012" }, + { type: "SSN", layer: "regex", ex: "1 85 12 75 108 112 48" }, + { type: "PERSON", layer: "ner", ex: "John Smith, Marie Dupont" }, + { type: "LOCATION", layer: "ner", ex: "Paris, 75001, rue de Rivoli" }, + { type: "ORGANIZATION", layer: "ner", ex: "Acme Corp, Banque de France" }, + { type: "DATE_TIME", layer: "ner", ex: "12/03/1985, le 3 mars" }, + { type: "NRP", layer: "ner", ex: "Nationality, religion, political party" }, + ].map((row) => ( + + + + + + ))} + +
TypeLayerExamples
{row.type} + + {row.layer} + + {row.ex}
+
+ +

Interactive Playground

+ + Try PII detection without authentication at{" "} + + http://localhost:8090/playground + + . The playground endpoint (POST /playground/analyze) is IP-rate-limited to + 20 requests/minute and requires no Bearer token. + +
+ ); +} diff --git a/web/src/pages/docs/components/ApiEndpoint.tsx b/web/src/pages/docs/components/ApiEndpoint.tsx new file mode 100644 index 0000000..adfeab3 --- /dev/null +++ b/web/src/pages/docs/components/ApiEndpoint.tsx @@ -0,0 +1,38 @@ +import { cn } from "@/lib/utils"; + +type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE"; + +interface ApiEndpointProps { + method: HttpMethod; + path: string; + description?: string; +} + +const methodColors: Record = { + GET: "bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-300", + POST: "bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-300", + PUT: "bg-amber-100 text-amber-700 dark:bg-amber-900/40 dark:text-amber-300", + PATCH: "bg-purple-100 text-purple-700 dark:bg-purple-900/40 dark:text-purple-300", + DELETE: "bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-300", +}; + +export function ApiEndpoint({ method, path, description }: ApiEndpointProps) { + return ( +
+
+ + {method} + + {path} +
+ {description && ( +
{description}
+ )} +
+ ); +} diff --git a/web/src/pages/docs/components/Callout.tsx b/web/src/pages/docs/components/Callout.tsx new file mode 100644 index 0000000..71676c0 --- /dev/null +++ b/web/src/pages/docs/components/Callout.tsx @@ -0,0 +1,64 @@ +import { Info, AlertTriangle, XCircle, Lightbulb } from "lucide-react"; +import { cn } from "@/lib/utils"; + +type CalloutType = "info" | "warning" | "danger" | "tip"; + +interface CalloutProps { + type?: CalloutType; + title?: string; + children: React.ReactNode; +} + +const configs: Record< + CalloutType, + { icon: React.ElementType; borderColor: string; bgColor: string; titleColor: string; iconColor: string } +> = { + info: { + icon: Info, + borderColor: "border-blue-400", + bgColor: "bg-blue-50 dark:bg-blue-950/30", + titleColor: "text-blue-800 dark:text-blue-200", + iconColor: "text-blue-500", + }, + warning: { + icon: AlertTriangle, + borderColor: "border-amber-400", + bgColor: "bg-amber-50 dark:bg-amber-950/30", + titleColor: "text-amber-800 dark:text-amber-200", + iconColor: "text-amber-500", + }, + danger: { + icon: XCircle, + borderColor: "border-red-400", + bgColor: "bg-red-50 dark:bg-red-950/30", + titleColor: "text-red-800 dark:text-red-200", + iconColor: "text-red-500", + }, + tip: { + icon: Lightbulb, + borderColor: "border-green-400", + bgColor: "bg-green-50 dark:bg-green-950/30", + titleColor: "text-green-800 dark:text-green-200", + iconColor: "text-green-500", + }, +}; + +export function Callout({ type = "info", title, children }: CalloutProps) { + const { icon: Icon, borderColor, bgColor, titleColor, iconColor } = configs[type]; + const defaultTitles: Record = { + info: "Note", + warning: "Warning", + danger: "Danger", + tip: "Tip", + }; + + return ( +
+ +
+

{title ?? defaultTitles[type]}

+
{children}
+
+
+ ); +} diff --git a/web/src/pages/docs/components/CodeBlock.tsx b/web/src/pages/docs/components/CodeBlock.tsx new file mode 100644 index 0000000..8f360cb --- /dev/null +++ b/web/src/pages/docs/components/CodeBlock.tsx @@ -0,0 +1,48 @@ +import { useState } from "react"; +import { Copy, Check } from "lucide-react"; +import { cn } from "@/lib/utils"; + +interface CodeBlockProps { + code: string; + language?: string; + className?: string; +} + +export function CodeBlock({ code, language = "bash", className }: CodeBlockProps) { + const [copied, setCopied] = useState(false); + + const handleCopy = async () => { + await navigator.clipboard.writeText(code.trim()); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + + return ( +
+ {/* Header bar */} +
+ {language} + +
+ {/* Code body */} +
+        {code.trim()}
+      
+
+ ); +} diff --git a/web/src/pages/docs/components/ParamTable.tsx b/web/src/pages/docs/components/ParamTable.tsx new file mode 100644 index 0000000..e60a0bb --- /dev/null +++ b/web/src/pages/docs/components/ParamTable.tsx @@ -0,0 +1,56 @@ +import { cn } from "@/lib/utils"; + +export interface Param { + name: string; + type: string; + required?: boolean; + description: string; + default?: string; +} + +interface ParamTableProps { + params: Param[]; + title?: string; +} + +export function ParamTable({ params, title = "Parameters" }: ParamTableProps) { + return ( +
+ {title &&

{title}

} +
+ + + + + + + + + + + {params.map((p, i) => ( + + + + + + + ))} + +
NameTypeRequiredDescription
+ {p.name} + + {p.type} + + {p.required ? ( + required + ) : ( + + optional{p.default ? ` (${p.default})` : ""} + + )} + {p.description}
+
+
+ ); +} diff --git a/web/src/pages/docs/components/TableOfContents.tsx b/web/src/pages/docs/components/TableOfContents.tsx new file mode 100644 index 0000000..fd5ce56 --- /dev/null +++ b/web/src/pages/docs/components/TableOfContents.tsx @@ -0,0 +1,94 @@ +import { useEffect, useState } from "react"; +import { cn } from "@/lib/utils"; +import { useLocation } from "react-router-dom"; + +interface TocItem { + id: string; + title: string; + level: number; +} + +function useTocItems(): TocItem[] { + const [items, setItems] = useState([]); + const location = useLocation(); + + useEffect(() => { + // Small delay to allow page to render + const timer = setTimeout(() => { + const headings = Array.from(document.querySelectorAll("[data-doc-content] h2, [data-doc-content] h3")); + const tocItems: TocItem[] = headings.map((el) => ({ + id: el.id, + title: el.textContent ?? "", + level: el.tagName === "H2" ? 2 : 3, + })); + setItems(tocItems); + }, 50); + return () => clearTimeout(timer); + }, [location.pathname]); + + return items; +} + +function useActiveId(ids: string[]): string { + const [activeId, setActiveId] = useState(""); + + useEffect(() => { + if (ids.length === 0) return; + + const observer = new IntersectionObserver( + (entries) => { + const visible = entries.filter((e) => e.isIntersecting); + if (visible.length > 0) { + setActiveId(visible[0].target.id); + } + }, + { rootMargin: "0% 0% -70% 0%", threshold: 0 } + ); + + ids.forEach((id) => { + const el = document.getElementById(id); + if (el) observer.observe(el); + }); + + return () => observer.disconnect(); + }, [ids]); + + return activeId; +} + +export function TableOfContents() { + const items = useTocItems(); + const activeId = useActiveId(items.map((i) => i.id)); + + if (items.length === 0) return null; + + return ( + + ); +} diff --git a/web/src/pages/docs/deployment/BlueGreenPage.tsx b/web/src/pages/docs/deployment/BlueGreenPage.tsx new file mode 100644 index 0000000..e5e335a --- /dev/null +++ b/web/src/pages/docs/deployment/BlueGreenPage.tsx @@ -0,0 +1,107 @@ +import { CodeBlock } from "../components/CodeBlock"; +import { Callout } from "../components/Callout"; + +export function BlueGreenPage() { + return ( +
+

Blue/Green Deployment

+

+ Veylant IA supports zero-downtime deployments via blue/green traffic switching with Istio. + Two Helm releases (veylant-proxy-blue and veylant-proxy-green) + run simultaneously; traffic is switched via an Istio VirtualService patch. +

+ + + Traffic rollback is under 5 seconds — just patch the VirtualService weight. + No pod restarts required. + + +

Architecture

+
+
{`Internet
+    │
+Istio Gateway
+    │
+VirtualService (veylant-proxy)
+    ├── weight: 100 → Service: veylant-proxy-blue   (ACTIVE)
+    └── weight:   0 → Service: veylant-proxy-green  (STANDBY)
+
+Helm releases:
+  veylant-proxy-blue:  3 replicas, image: 1.0.0  ← current production
+  veylant-proxy-green: 3 replicas, image: 1.1.0  ← new version (deployed but no traffic)`}
+
+ +

Deploying a New Version

+ + +

Rollback

+ + +

Make Commands

+
+ + + + + + + + + {[ + { cmd: "make deploy-blue IMAGE_TAG=X.Y.Z", action: "Deploy IMAGE_TAG to blue slot (Helm upgrade)" }, + { cmd: "make deploy-green IMAGE_TAG=X.Y.Z", action: "Deploy IMAGE_TAG to green slot (Helm upgrade)" }, + { cmd: "make deploy-rollback ACTIVE_SLOT=blue", action: "Switch 100% traffic to blue slot" }, + { cmd: "make deploy-rollback ACTIVE_SLOT=green", action: "Switch 100% traffic to green slot" }, + { cmd: "make helm-dry-run", action: "Render Helm templates without deploying" }, + { cmd: "make helm-deploy IMAGE_TAG=X.Y.Z", action: "Deploy to staging (requires KUBECONFIG)" }, + ].map((row) => ( + + + + + ))} + +
CommandAction
{row.cmd}{row.action}
+
+ +

Release Pipeline

+

+ Tagging a version (v*) triggers the GitHub Actions release pipeline: +

+
    +
  1. Multi-arch Docker build (amd64/arm64) → pushed to GHCR
  2. +
  3. Helm chart packaged as OCI artifact → pushed to GHCR
  4. +
  5. GitHub Release created with CHANGELOG.md notes extracted automatically
  6. +
  7. Trivy image scan (CRITICAL/HIGH blocking)
  8. +
  9. gitleaks secret detection
  10. +
+
+ ); +} diff --git a/web/src/pages/docs/deployment/DockerPage.tsx b/web/src/pages/docs/deployment/DockerPage.tsx new file mode 100644 index 0000000..2194cd9 --- /dev/null +++ b/web/src/pages/docs/deployment/DockerPage.tsx @@ -0,0 +1,71 @@ +import { CodeBlock } from "../components/CodeBlock"; +import { Callout } from "../components/Callout"; + +export function DockerPage() { + return ( +
+

Docker Compose Deployment

+

+ For small to medium deployments (single server, staging), Docker Compose is the recommended + approach. The production configuration uses the same services as local development with + hardened settings. +

+ +

Production Configuration

+ + Ensure you have set: server.env=production, a strong crypto.key, + TLS certificates for all services, PostgreSQL with TLS, and proper secrets management + (HashiCorp Vault recommended). + + + + +

Building the Production Image

+ + +

Health Checks

+ + +

Database Backup

+ backup-$(date +%Y%m%d).sql.gz + +# Restore +gunzip -c backup-20260115.sql.gz | psql -h postgres -U veylant -d veylant`} + /> +
+ ); +} diff --git a/web/src/pages/docs/deployment/KubernetesPage.tsx b/web/src/pages/docs/deployment/KubernetesPage.tsx new file mode 100644 index 0000000..875950c --- /dev/null +++ b/web/src/pages/docs/deployment/KubernetesPage.tsx @@ -0,0 +1,117 @@ +import { CodeBlock } from "../components/CodeBlock"; +import { Callout } from "../components/Callout"; + +export function KubernetesPage() { + return ( +
+

Kubernetes Deployment (Helm)

+

+ Veylant IA ships a production-grade Helm chart at{" "} + deploy/helm/veylant-proxy/. The chart includes Deployment, Service, HPA, + PodDisruptionBudget, and ServiceMonitor resources. +

+ +

Prerequisites

+
    +
  • Kubernetes 1.28+ (EKS v1.31 tested)
  • +
  • Helm 3.12+
  • +
  • Metrics Server (for HPA)
  • +
  • Prometheus Operator (optional, for ServiceMonitor)
  • +
  • Istio 1.20+ (for blue/green deployment)
  • +
+ +

Install the Chart

+ + +

Production Values

+ + +

Horizontal Pod Autoscaler

+

+ The HPA scales from 3 to 15 replicas based on CPU (70%) and memory (80%) utilization. + Scale-up is fast (30s stabilization); scale-down is conservative (5 minutes) to avoid + thrashing. +

+ + +

Terraform (AWS EKS)

+

+ Infrastructure as Code for a production EKS cluster is in{" "} + deploy/terraform/: +

+
    +
  • EKS v1.31, 3-AZ node groups (t3.large)
  • +
  • S3 bucket for PostgreSQL backups (7-day retention)
  • +
  • IRSA for pod-level AWS permissions
  • +
  • VPC, subnets, security groups
  • +
+ + + + Configure remote state (S3 + DynamoDB locking) before running in production. The default + local state is not suitable for team use. + +
+ ); +} diff --git a/web/src/pages/docs/getting-started/KeyConceptsPage.tsx b/web/src/pages/docs/getting-started/KeyConceptsPage.tsx new file mode 100644 index 0000000..7442943 --- /dev/null +++ b/web/src/pages/docs/getting-started/KeyConceptsPage.tsx @@ -0,0 +1,127 @@ +import { Callout } from "../components/Callout"; + +const concepts = [ + { + term: "Tenant", + definition: + "A logical unit of isolation — typically a company or business unit. All data in PostgreSQL is isolated by tenant_id via Row-Level Security. A tenant can have multiple users, multiple routing rules, and separate cost quotas.", + }, + { + term: "Routing Rule", + definition: + "A policy that matches incoming AI requests based on conditions (user role, department, model, token estimate, sensitivity) and routes them to a specific provider with optional fallback. Rules are sorted by priority (lower = evaluated first). First match wins.", + }, + { + term: "PII (Personally Identifiable Information)", + definition: + "Data that can identify a person: names, email addresses, phone numbers, IBANs, SSNs, credit card numbers, etc. Veylant IA detects and anonymizes PII in prompts before they leave your network.", + }, + { + term: "Pseudonymization", + definition: + "A reversible PII replacement technique. Detected PII tokens are replaced with synthetic identifiers (e.g., PERSON_001) and the original→synthetic mapping is stored in Redis (AES-256-GCM encrypted, TTL-based). The LLM works with the synthetic data; the response can optionally be de-pseudonymized.", + }, + { + term: "Audit Log", + definition: + "An immutable record of every AI request: tenant, user, model, provider, token counts, cost, PII entities detected, policy matched, latency, and response status. Stored in ClickHouse (append-only). Retention via TTL policies — no DELETE operations.", + }, + { + term: "Provider Adapter", + definition: + "A Go interface (Send, Stream, Validate, HealthCheck) implemented for each LLM provider. The routing engine selects the adapter; all adapters return OpenAI-format responses regardless of the upstream API.", + }, + { + term: "Circuit Breaker", + definition: + "A per-provider failure counter. When failures exceed a threshold (default: 5), the breaker opens and the provider is bypassed for a TTL period (default: 60s). The fallback chain in the routing rule is used instead.", + }, + { + term: "RBAC", + definition: + "Role-Based Access Control. Four roles: admin (full access), manager (read-write policies and users), user (inference only, restricted models), auditor (read-only logs and compliance, no inference). Roles are embedded in the Keycloak JWT.", + }, + { + term: "Feature Flag", + definition: + "A boolean or string flag stored in PostgreSQL with an in-memory cache. Used to gate features without redeployment. Falls back to in-memory defaults if the database is unavailable.", + }, + { + term: "GDPR Article 30", + definition: + "The GDPR requirement to maintain a Record of Processing Activities (ROPA). Veylant IA provides a built-in registry with fields for use case, legal basis, data categories, retention period, recipients, and processors.", + }, + { + term: "EU AI Act", + definition: + "EU regulation classifying AI systems by risk level: forbidden, high, limited, or minimal. Veylant IA's compliance module helps you classify each use case through a structured questionnaire and generates PDF reports.", + }, + { + term: "SLO (Service Level Objective)", + definition: + "Veylant IA targets 99.5% availability and p95 latency < 500ms. These are tracked in the production Grafana dashboard with an error budget that updates in real time.", + }, +]; + +export function KeyConceptsPage() { + return ( +
+

Key Concepts

+

+ This glossary explains the core abstractions you'll encounter when working with Veylant IA. +

+ + + If you're new to Veylant IA, read{" "} + What is Veylant IA? first, then come back here before + diving into the API reference or guides. + + +

Glossary

+ +
+ {concepts.map((c) => ( +
+
{c.term}
+
{c.definition}
+
+ ))} +
+ +

Request Lifecycle

+

What happens when a client sends a request to POST /v1/chat/completions:

+ +
+
{`1. Request arrives at Go proxy (:8090)
+2. RequestID middleware       → generate X-Request-ID
+3. SecurityHeaders middleware → set CSP, HSTS, COOP headers
+4. CORS middleware            → validate Origin header
+5. Auth middleware            → validate Bearer JWT (Keycloak or mock)
+   → extract tenant_id, user_id, role, department from claims
+6. RateLimit middleware       → check per-tenant token bucket (Redis)
+   → if exceeded: 429 with Retry-After header
+7. RBAC check                 → validate role has access to requested model
+8. Routing engine             → evaluate rules (priority ASC, first match)
+   → select provider + fallback chain
+9. PII detection              → gRPC call to PII service (<50ms budget)
+   → anonymize/pseudonymize prompt
+10. Circuit breaker check     → skip if provider is open
+11. Provider adapter          → forward to LLM (stream or batch)
+12. Audit logger              → async ClickHouse write (non-blocking)
+13. Response returned to client`}
+
+ +

Multi-tenancy Model

+

+ Veylant IA uses logical isolation via PostgreSQL Row-Level Security (RLS). + The application connects as role veylant_app and sets{" "} + app.tenant_id per session using a middleware. All queries automatically filter + by tenant without requiring explicit WHERE clauses in application code. +

+

+ Physical isolation (separate database instances per tenant) is a V2 feature. See the + feedback backlog. +

+
+ ); +} diff --git a/web/src/pages/docs/getting-started/QuickStartPage.tsx b/web/src/pages/docs/getting-started/QuickStartPage.tsx new file mode 100644 index 0000000..dbd9191 --- /dev/null +++ b/web/src/pages/docs/getting-started/QuickStartPage.tsx @@ -0,0 +1,167 @@ +import { CodeBlock } from "../components/CodeBlock"; +import { Callout } from "../components/Callout"; + +export function QuickStartPage() { + return ( +
+

Quick Start

+

+ This guide gets you from zero to a running Veylant IA instance in under 5 minutes using + Docker Compose. +

+ + + You need Docker 24+ and Docker Compose v2 installed. + Clone the repository and ensure ports 8090, 8080, 5432, 6379, 8123, 3000, and 3001 are + free. + + +

Step 1 — Clone the repository

+ + +

Step 2 — Configure environment

+

+ Copy the sample environment file and add at least one LLM provider API key. For a minimal + setup, OpenAI is enough. +

+ + + + In server.env=development (the default), all external services degrade + gracefully. Keycloak is bypassed (mock JWT), PostgreSQL failures disable routing, ClickHouse + failures disable audit logs. This means you can start the proxy even if some services + haven't fully initialized yet. + + +

Step 3 — Start the stack

+ +

+ This starts 9 services: PostgreSQL, Redis, ClickHouse, Keycloak, the Go proxy, PII + detection service, Prometheus, Grafana, and the React dashboard. +

+

+ Wait for the proxy to print server listening on :8090. First startup takes + ~2 minutes while Keycloak initializes and database migrations run. +

+ +

Step 4 — Verify the stack

+ + +
+ + + + + + + + + + {[ + { service: "AI Proxy", url: "http://localhost:8090", creds: "—" }, + { service: "React Dashboard", url: "http://localhost:3000", creds: "dev mode (no auth)" }, + { service: "Keycloak Admin", url: "http://localhost:8080", creds: "admin / admin" }, + { service: "Grafana", url: "http://localhost:3001", creds: "admin / admin" }, + { service: "Prometheus", url: "http://localhost:9090", creds: "—" }, + { service: "API Docs", url: "http://localhost:8090/docs", creds: "—" }, + { service: "Playground", url: "http://localhost:8090/playground", creds: "—" }, + ].map((row) => ( + + + + + + ))} + +
ServiceURLCredentials
{row.service} + {row.url} + {row.creds}
+
+ +

Step 5 — Make your first AI call

+

+ In development mode, the proxy uses a mock JWT verifier. Pass any Bearer token and the + request will be authenticated as admin@veylant.dev. +

+ + +

Or use the OpenAI Python SDK with a changed base URL:

+ + +

Step 6 — Explore the dashboard

+

+ Open http://localhost:3000 to see the React dashboard. In development mode, + you're automatically logged in as Dev Admin. You'll see: +

+
    +
  • + Overview — request counts, costs, and tokens consumed +
  • +
  • + Playground IA — test prompts with live PII detection visualization +
  • +
  • + Policies — create and manage routing rules +
  • +
  • + Compliance — GDPR Article 30 registry and AI Act questionnaire +
  • +
+ + + Try creating a routing rule in the dashboard that sends all requests from the{" "} + legal department to Anthropic instead of OpenAI. See{" "} + Routing Rules Engine for the full guide. + + +

Stop the stack

+ +
+ ); +} diff --git a/web/src/pages/docs/getting-started/WhatIsVeylantPage.tsx b/web/src/pages/docs/getting-started/WhatIsVeylantPage.tsx new file mode 100644 index 0000000..3bdf927 --- /dev/null +++ b/web/src/pages/docs/getting-started/WhatIsVeylantPage.tsx @@ -0,0 +1,172 @@ +import { Callout } from "../components/Callout"; +import { Link } from "react-router-dom"; + +export function WhatIsVeylantPage() { + return ( +
+

What is Veylant IA?

+

+ Veylant IA is a B2B SaaS platform that acts as an intelligent proxy and + gateway for enterprise AI consumption. It sits between your organization's applications and + LLM providers (OpenAI, Anthropic, Azure, Mistral, Ollama), enforcing governance policies on + every request. +

+ + + Veylant IA implements the OpenAI API format — specifically{" "} + /v1/chat/completions. Your existing OpenAI SDK clients work without + modification; just change the base URL. + + +

Core Value Proposition

+

Four problems Veylant IA solves for enterprise IT and compliance teams:

+ +
+ {[ + { + title: "Shadow AI Prevention", + desc: "Every AI call flows through the proxy — no direct provider access possible. Full audit trail of who sent what to which model.", + }, + { + title: "PII Anonymization", + desc: "3-layer detection (regex + spaCy NER + LLM validation) anonymizes sensitive data before it leaves your perimeter. <50ms latency.", + }, + { + title: "GDPR & EU AI Act Compliance", + desc: "Built-in Article 30 processing registry, AI Act risk classification, DPIA templates, and subject access/erasure rights.", + }, + { + title: "Cost Control", + desc: "Per-tenant and per-user token budgets, circuit breakers per provider, cost breakdown by department and model.", + }, + ].map((card) => ( +
+

{card.title}

+

{card.desc}

+
+ ))} +
+ +

Architecture Overview

+

+ Veylant IA is a modular monolith (not microservices) with two distinct + runtimes: +

+ +
+
{`API Gateway (Traefik / Nginx)
+        │
+        ▼
+Go Proxy [cmd/proxy] — :8090
+  ├── Auth middleware     (OIDC/Keycloak JWT validation)
+  ├── Rate limiter        (token-bucket, per-tenant + per-user)
+  ├── PII gRPC client     → PII Service :50051
+  ├── Routing engine      (PostgreSQL JSONB rules, in-memory cache)
+  ├── Provider adapters   (OpenAI / Anthropic / Azure / Mistral / Ollama)
+  ├── Audit logger        → ClickHouse (async batch)
+  └── Admin REST API      (/v1/admin/*)
+        │ gRPC <2ms
+        ▼
+PII Detection Service [FastAPI + grpc.aio] — :8091 / :50051
+  ├── Layer 1: Regex      (IBAN, email, phone, SSN, credit cards)
+  ├── Layer 2: Presidio + spaCy NER (names, addresses, orgs)
+  └── Layer 3: LLM validation (ambiguous cases)
+        │
+        ▼
+LLM Provider (OpenAI / Anthropic / Azure / Mistral / Ollama)
+
+Data Layer:
+  PostgreSQL 16   — config, users, policies, registry (RLS multi-tenancy)
+  ClickHouse      — append-only audit logs and analytics
+  Redis 7         — sessions, rate limiting, PII pseudonymization mappings
+  Keycloak        — IAM, SSO, SAML 2.0 / OIDC federation`}
+
+ +

Key Components

+ +

Go Proxy

+

+ The core of Veylant IA. Written in Go 1.24, it handles all incoming AI requests, applies + governance policies, and routes them to the appropriate LLM provider. It exposes: +

+
    +
  • + /v1/chat/completions — OpenAI-compatible inference endpoint +
  • +
  • + /v1/pii/analyze — on-demand PII detection +
  • +
  • + /v1/admin/* — governance API (policies, users, audit logs, compliance) +
  • +
  • + /healthz, /docs, /playground +
  • +
+ +

PII Detection Service

+

+ A Python FastAPI + gRPC service running 3 detection layers in under 50ms. Anonymizes or + pseudonymizes PII in prompts before they reach the upstream LLM. Pseudonymized mappings are + stored in Redis (AES-256-GCM encrypted, TTL-based). +

+ +

Routing Engine

+

+ Rules stored in PostgreSQL (JSONB conditions), cached in memory. Routes requests to + providers based on user role, department, model requested, sensitivity score, token + estimate, and more. First-match wins; lower priority number = evaluated first. +

+ +

Supported LLM Providers

+
+ + + + + + + + + + + {[ + { provider: "OpenAI", models: "gpt-4o, gpt-4-turbo, gpt-3.5-turbo", stream: "Yes", status: "GA" }, + { provider: "Anthropic", models: "claude-3-5-sonnet, claude-3-opus", stream: "Yes", status: "GA" }, + { provider: "Azure OpenAI", models: "GPT-4 deployments", stream: "Yes", status: "GA" }, + { provider: "Mistral AI", models: "mistral-large, mistral-medium", stream: "Yes", status: "GA" }, + { provider: "Ollama", models: "llama3, mistral, codellama, ...", stream: "Yes", status: "GA" }, + ].map((row) => ( + + + + + + + ))} + +
ProviderModelsStreamingStatus
{row.provider}{row.models}{row.stream} + + {row.status} + +
+
+ +

Next Steps

+
    +
  • + Quick Start — run the full stack in 5 + minutes +
  • +
  • + Key Concepts — learn the core + abstractions +
  • +
  • + API Reference — integrate your first + application +
  • +
+
+ ); +} diff --git a/web/src/pages/docs/guides/CircuitBreakerGuide.tsx b/web/src/pages/docs/guides/CircuitBreakerGuide.tsx new file mode 100644 index 0000000..581434c --- /dev/null +++ b/web/src/pages/docs/guides/CircuitBreakerGuide.tsx @@ -0,0 +1,118 @@ +import { Callout } from "../components/Callout"; +import { CodeBlock } from "../components/CodeBlock"; + +export function CircuitBreakerGuide() { + return ( +
+

Circuit Breaker & Failover

+

+ Veylant IA includes a per-provider circuit breaker that prevents cascading failures when an + LLM provider is degraded or unreachable. +

+ +

Circuit Breaker States

+
+ {[ + { + state: "Closed", + color: "border-green-400", + bg: "bg-green-50 dark:bg-green-950/30", + desc: "Normal operation. Requests are forwarded to the provider. Failures are counted.", + }, + { + state: "Open", + color: "border-red-400", + bg: "bg-red-50 dark:bg-red-950/30", + desc: "Provider bypassed. All requests use the fallback chain. Stays open for open_ttl seconds.", + }, + { + state: "Half-Open", + color: "border-amber-400", + bg: "bg-amber-50 dark:bg-amber-950/30", + desc: "Testing if provider has recovered. One probe request sent. Success → Closed; Failure → Open.", + }, + ].map((item) => ( +
+

{item.state}

+

{item.desc}

+
+ ))} +
+ +

Configuration

+ + + + Each provider has an independent circuit breaker. A failing Azure deployment does not affect + OpenAI or Anthropic calls. + + +

Fallback Chain

+

+ When the primary provider's circuit is open, the routing engine uses the{" "} + fallback_providers array from the matched routing rule: +

+ + +

Checking Status

+ + +

Prometheus Alert

+

+ The CircuitBreakerOpen alert fires when any provider is in the open state: +

+ 0 + for: 0m + labels: + severity: warning + annotations: + summary: "Provider {{ $labels.provider }} circuit breaker is open" + description: "The circuit breaker for {{ $labels.provider }} has opened after repeated failures."`} + /> + +

Development Mode Degradation

+ + In server.env=development, Veylant IA degrades gracefully if services are + unreachable: +
    +
  • Keycloak unreachable → MockVerifier (auth bypassed)
  • +
  • PostgreSQL unreachable → routing disabled, feature flags use in-memory defaults
  • +
  • ClickHouse unreachable → audit logging disabled
  • +
  • PII service unreachable → PII skipped if fail_open=true
  • +
+ In production mode, any of the above causes a fatal startup error. +
+
+ ); +} diff --git a/web/src/pages/docs/guides/ComplianceGuide.tsx b/web/src/pages/docs/guides/ComplianceGuide.tsx new file mode 100644 index 0000000..b58d802 --- /dev/null +++ b/web/src/pages/docs/guides/ComplianceGuide.tsx @@ -0,0 +1,154 @@ +import { Callout } from "../components/Callout"; +import { CodeBlock } from "../components/CodeBlock"; +import { Link } from "react-router-dom"; + +export function ComplianceGuide() { + return ( +
+

GDPR & EU AI Act Compliance

+

+ Veylant IA includes a built-in compliance module for GDPR Article 30 record-keeping and EU + AI Act risk classification. It is designed to serve as your primary compliance tool for AI + deployments. +

+ +

GDPR Article 30 — Record of Processing Activities

+

+ Article 30 requires organizations to maintain a written record of all data processing + activities. For AI systems, this means documenting each use case where personal data may be + processed. +

+ +

Required ROPA Fields

+
+ + + + + + + + + + {[ + { field: "use_case_name", req: "Name of the processing activity", ex: "Legal contract analysis" }, + { field: "purpose", req: "Art. 5(1)(b) — purpose limitation", ex: "Automated risk identification in supplier contracts" }, + { field: "legal_basis", req: "Art. 6 — lawfulness of processing", ex: "legitimate_interest" }, + { field: "data_categories", req: "Art. 30(1)(c) — categories of data subjects and data", ex: "name, email, financial" }, + { field: "retention_period", req: "Art. 5(1)(e) — storage limitation", ex: "3 years" }, + { field: "security_measures", req: "Art. 32 — security of processing", ex: "AES-256-GCM, PII anonymization" }, + { field: "controller_name", req: "Art. 30(1)(a) — controller identity", ex: "Acme Corp — dpo@acme.com" }, + { field: "processors", req: "Art. 30(1)(d) — recipients of data", ex: "Anthropic (via Veylant IA proxy)" }, + ].map((row) => ( + + + + + + ))} + +
FieldGDPR RequirementExample
{row.field}{row.req}{row.ex}
+
+ + +
    +
  • consent — User has given explicit consent (Art. 6(1)(a))
  • +
  • contract — Processing necessary for a contract (Art. 6(1)(b))
  • +
  • legal_obligation — Required by law (Art. 6(1)(c))
  • +
  • vital_interests — Protecting someone's life (Art. 6(1)(d))
  • +
  • public_task — Public interest or official authority (Art. 6(1)(e))
  • +
  • legitimate_interest — Legitimate interests of the controller (Art. 6(1)(f))
  • +
+ +

EU AI Act Risk Classification

+

+ The EU AI Act (effective August 2024, full enforcement from August 2026) classifies AI + systems into four risk categories. +

+ +
+ {[ + { + level: "Forbidden", + color: "border-red-400 bg-red-50 dark:bg-red-950/30", + badge: "bg-red-100 dark:bg-red-900/40 text-red-700 dark:text-red-300", + score: "Score 5", + desc: "Cannot be deployed. Examples: social scoring systems, real-time biometric surveillance in public spaces, AI that exploits vulnerable groups.", + }, + { + level: "High Risk", + color: "border-orange-400 bg-orange-50 dark:bg-orange-950/30", + badge: "bg-orange-100 dark:bg-orange-900/40 text-orange-700 dark:text-orange-300", + score: "Score 3–4", + desc: "Requires conformity assessment before deployment. DPIA mandatory. Examples: AI in hiring, credit scoring, education grading, critical infrastructure.", + }, + { + level: "Limited Risk", + color: "border-amber-400 bg-amber-50 dark:bg-amber-950/30", + badge: "bg-amber-100 dark:bg-amber-900/40 text-amber-700 dark:text-amber-300", + score: "Score 1–2", + desc: "Transparency obligations apply. Users must be informed they interact with AI. Examples: chatbots, recommendation systems, customer service AI.", + }, + { + level: "Minimal Risk", + color: "border-green-400 bg-green-50 dark:bg-green-950/30", + badge: "bg-green-100 dark:bg-green-900/40 text-green-700 dark:text-green-300", + score: "Score 0", + desc: "Minimal or no risk. Voluntary code of conduct recommended. Examples: spam filters, AI-powered search, content recommendation.", + }, + ].map((item) => ( +
+
+
+ + {item.level} + + {item.score} +
+

{item.desc}

+
+
+ ))} +
+ +

Data Protection Impact Assessment (DPIA)

+

+ A DPIA is mandatory under GDPR Art. 35 for high-risk processing activities. High-risk AI + systems under the AI Act also trigger DPIA requirements. Veylant IA generates DPIA template + documents from the Admin → Compliance → Reports tab. +

+ +

Compliance Reports

+

Available report formats via the API:

+ + + + All accesses to compliance reports and audit logs are themselves logged. This satisfies + data protection authority requirements for meta-logging of sensitive data access. + + +

Working with Compliance

+

+ See the Admin — Compliance API for full + endpoint documentation, or navigate to{" "} + Dashboard → Compliance to use the visual interface. +

+
+ ); +} diff --git a/web/src/pages/docs/guides/MonitoringGuide.tsx b/web/src/pages/docs/guides/MonitoringGuide.tsx new file mode 100644 index 0000000..5d3b8e5 --- /dev/null +++ b/web/src/pages/docs/guides/MonitoringGuide.tsx @@ -0,0 +1,162 @@ +import { Callout } from "../components/Callout"; +import { CodeBlock } from "../components/CodeBlock"; + +export function MonitoringGuide() { + return ( +
+

Monitoring & Alerting

+

+ Veylant IA exposes Prometheus metrics and ships pre-built Grafana dashboards and + Alertmanager rules for production operations. +

+ +

Prometheus Metrics

+

+ The proxy exposes metrics at GET /metrics (Prometheus text format, scraped + every 15 seconds). +

+ +
+ + + + + + + + + + {[ + { metric: "veylant_requests_total", type: "Counter", desc: "Total requests by provider, model, status_code, tenant_id" }, + { metric: "veylant_request_duration_seconds", type: "Histogram", desc: "End-to-end request latency (p50, p95, p99)" }, + { metric: "veylant_pii_entities_total", type: "Counter", desc: "PII entities detected by type" }, + { metric: "veylant_tokens_total", type: "Counter", desc: "Tokens consumed by provider and model" }, + { metric: "veylant_cost_usd_total", type: "Counter", desc: "USD cost by provider and model" }, + { metric: "veylant_circuit_breaker_state", type: "Gauge", desc: "Circuit breaker state (0=closed, 1=open, 2=half-open) by provider" }, + { metric: "veylant_rate_limit_rejections_total", type: "Counter", desc: "Rate limit rejections by tenant_id" }, + { metric: "veylant_pii_pipeline_duration_seconds", type: "Histogram", desc: "PII detection pipeline latency" }, + { metric: "veylant_db_connections", type: "Gauge", desc: "PostgreSQL connection pool (open, idle, in-use)" }, + ].map((row) => ( + + + + + + ))} + +
MetricTypeDescription
{row.metric} + + {row.type} + + {row.desc}
+
+ +

Grafana Dashboards

+

+ Two dashboards are pre-provisioned in Grafana (http://localhost:3001, + admin/admin): +

+ +
+
+

Proxy Overview

+
    +
  • Request rate (req/min)
  • +
  • Error rate (%)
  • +
  • Latency p50/p95/p99
  • +
  • Token consumption over time
  • +
  • PII entities by type
  • +
  • Provider distribution (pie chart)
  • +
  • DB connection pool
  • +
+
+
+

Production SLO

+
    +
  • SLO: 99.5% availability
  • +
  • Error budget (burn rate)
  • +
  • p95 latency SLO (<500ms)
  • +
  • Error budget remaining (%)
  • +
  • Active incidents timeline
  • +
+
+
+ +

Prometheus Alerts

+

+ 7 alert rules are configured in deploy/prometheus/rules.yml and integrated + with Alertmanager: +

+ +
+ {[ + { name: "HighLatency", severity: "warning", desc: "p95 latency > 500ms for 2 minutes", expr: "histogram_quantile(0.95, veylant_request_duration_seconds_bucket) > 0.5" }, + { name: "HighErrorRate", severity: "critical", desc: "Error rate > 5% for 1 minute", expr: "rate(veylant_requests_total{status_code=~'5..'}[1m]) / rate(veylant_requests_total[1m]) > 0.05" }, + { name: "CircuitBreakerOpen", severity: "warning", desc: "Any provider circuit breaker is open", expr: "veylant_circuit_breaker_state > 0" }, + { name: "ProxyDown", severity: "critical", desc: "Proxy health check failing", expr: "up{job='veylant-proxy'} == 0" }, + { name: "CertExpiry", severity: "warning", desc: "TLS certificate expires in < 7 days", expr: "probe_ssl_earliest_cert_expiry - time() < 7 * 86400" }, + { name: "DBConnPoolExhausted", severity: "warning", desc: "DB connection pool > 90% utilized", expr: "veylant_db_connections{state='in_use'} / (veylant_db_connections{state='in_use'} + veylant_db_connections{state='idle'}) > 0.9" }, + { name: "PIIAnomaly", severity: "warning", desc: "PII detection rate spiked > 3x baseline", expr: "rate(veylant_pii_entities_total[5m]) > 3 * avg_over_time(rate(veylant_pii_entities_total[5m])[1h:])" }, + ].map((alert) => ( +
+
+ {alert.name} + + {alert.severity} + +
+

{alert.desc}

+ + {alert.expr} + +
+ ))} +
+ +

Alertmanager Routing

+

Alerts are routed based on severity:

+
    +
  • + Critical → PagerDuty (immediate on-call notification) +
  • +
  • + Warning → Slack (#veylant-alerts channel) +
  • +
+ + +

SLO Definition

+ + Availability: 99.5% (allows ~3.6 hours downtime/month) +
+ Latency: p95 < 500ms for chat completion requests +
+ PII pipeline: p99 < 50ms +
+ Error rate: < 0.5% (5xx responses) +
+
+ ); +} diff --git a/web/src/pages/docs/guides/PiiGuide.tsx b/web/src/pages/docs/guides/PiiGuide.tsx new file mode 100644 index 0000000..4b651d6 --- /dev/null +++ b/web/src/pages/docs/guides/PiiGuide.tsx @@ -0,0 +1,147 @@ +import { CodeBlock } from "../components/CodeBlock"; +import { Callout } from "../components/Callout"; + +export function PiiGuide() { + return ( +
+

PII Detection & Anonymization

+

+ Veylant IA intercepts all AI prompts and runs a 3-layer PII detection pipeline before + forwarding to the LLM. The entire pipeline must complete in under 50ms. +

+ +

Detection Pipeline

+
+
{`Incoming prompt text
+        │
+        ▼
+Layer 1: Regex (< 1ms)
+  ─ IBAN: /[A-Z]{2}\\d{2}[A-Z0-9]{11,30}/
+  ─ Email: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}/
+  ─ Phone: /(?:\\+33|0)[1-9](?:[\\s.-]?\\d{2}){4}/
+  ─ SSN: /\\d{1}\\s?\\d{2}\\s?\\d{2}\\s?\\d{2}\\s?\\d{3}\\s?\\d{3}\\s?\\d{2}/
+  ─ Credit card: /\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}/
+        │
+        ▼
+Layer 2: Presidio + spaCy NER (15–40ms)
+  ─ PERSON: "John Smith", "Marie Dupont"
+  ─ LOCATION: "Paris", "75001", "rue de Rivoli"
+  ─ ORGANIZATION: "Acme Corp", "Banque de France"
+  ─ DATE_TIME: "12/03/1985"
+  ─ NRP: nationality, religion, political group
+        │
+        ▼
+Layer 3: LLM validation (optional, V1.1)
+  ─ For ambiguous cases scored 0.5–0.8
+  ─ Sends short validation prompts to a fast LLM
+  ─ Corrects false positives and false negatives
+        │
+        ▼
+Anonymized prompt → forwarded to LLM provider`}
+
+ +

Anonymization vs Pseudonymization

+

Veylant IA supports two modes:

+ +
+
+

Anonymization (default)

+

+ PII is replaced with a type label. No mapping stored. Irreversible. +

+ +
+
+

Pseudonymization

+

+ PII is replaced with a synthetic token. Mapping stored in Redis (encrypted, TTL-based). + Reversible. +

+ +
+
+ +

Redis Pseudonymization Mapping

+

+ When pseudonymization is enabled, the PII service stores mappings in Redis using the + following key structure: +

+ + +

Latency Budget

+
+ + + + + + + + + + {[ + { layer: "Layer 1: Regex", typ: "< 1ms", p99: "< 2ms" }, + { layer: "Layer 2: NER (spaCy)", typ: "15–30ms", p99: "45ms" }, + { layer: "Redis write (pseudonymization)", typ: "< 2ms", p99: "5ms" }, + { layer: "Total pipeline (gRPC)", typ: "20–35ms", p99: "50ms" }, + ].map((row) => ( + + + + + + ))} + +
LayerTypical latencyP99 latency
{row.layer}{row.typ}{row.p99}
+
+ + + If the PII service is unreachable and pii.fail_open=true (default in + development), requests are forwarded without anonymization and a warning is logged. In + production, set pii.fail_open=false to return 503 instead. + + +

Disabling PII per Request

+

+ There is no per-request PII bypass. PII is controlled globally via the{" "} + pii_detection feature flag or the pii.enabled config key. + Fine-grained per-rule PII control is planned for V1.1. +

+ +

Testing PII Detection

+ + + +
+ ); +} diff --git a/web/src/pages/docs/guides/RbacGuide.tsx b/web/src/pages/docs/guides/RbacGuide.tsx new file mode 100644 index 0000000..a559ba7 --- /dev/null +++ b/web/src/pages/docs/guides/RbacGuide.tsx @@ -0,0 +1,126 @@ +import { Callout } from "../components/Callout"; +import { CodeBlock } from "../components/CodeBlock"; + +export function RbacGuide() { + return ( +
+

RBAC & Permissions

+

+ Veylant IA enforces Role-Based Access Control on every request. Roles are embedded in the + Keycloak JWT and cannot be elevated at runtime. +

+ +

Roles

+
+ {[ + { + role: "admin", + color: "bg-red-100 dark:bg-red-900/40 text-red-700 dark:text-red-300", + description: "Full access. Can manage policies, users, providers, feature flags, and read all compliance/audit data. Has unrestricted model access.", + }, + { + role: "manager", + color: "bg-amber-100 dark:bg-amber-900/40 text-amber-700 dark:text-amber-300", + description: "Read-write access to routing policies and users. Can run AI inference with any model. Cannot manage feature flags or access compliance reports.", + }, + { + role: "user", + color: "bg-blue-100 dark:bg-blue-900/40 text-blue-700 dark:text-blue-300", + description: "Inference only. Restricted to the model list in rbac.user_allowed_models (default: gpt-4o-mini, mistral-medium). No admin API access.", + }, + { + role: "auditor", + color: "bg-purple-100 dark:bg-purple-900/40 text-purple-700 dark:text-purple-300", + description: "Read-only access to audit logs and compliance data. Cannot call /v1/chat/completions. Intended for compliance officers and DPOs.", + }, + ].map((item) => ( +
+ + {item.role} + +

{item.description}

+
+ ))} +
+ +

Permission Matrix

+
+ + + + + + + + + + + + {[ + { ep: "POST /v1/chat/completions", admin: "✓", manager: "✓", user: "✓ (limited models)", auditor: "✗" }, + { ep: "POST /v1/pii/analyze", admin: "✓", manager: "✓", user: "✓", auditor: "✓" }, + { ep: "GET /v1/admin/policies", admin: "✓", manager: "✓", user: "✗", auditor: "✗" }, + { ep: "POST/PUT/DELETE /v1/admin/policies", admin: "✓", manager: "✓", user: "✗", auditor: "✗" }, + { ep: "GET/POST/PUT /v1/admin/users", admin: "✓", manager: "read only", user: "✗", auditor: "✗" }, + { ep: "GET /v1/admin/logs", admin: "✓", manager: "✓", user: "✗", auditor: "✓" }, + { ep: "GET /v1/admin/costs", admin: "✓", manager: "✓", user: "✗", auditor: "✓" }, + { ep: "GET /v1/admin/compliance/*", admin: "✓", manager: "✗", user: "✗", auditor: "✓" }, + { ep: "POST /v1/admin/compliance/*", admin: "✓", manager: "✗", user: "✗", auditor: "✗" }, + { ep: "GET/PUT/DELETE /v1/admin/flags", admin: "✓", manager: "✗", user: "✗", auditor: "✗" }, + { ep: "GET /v1/admin/providers/status", admin: "✓", manager: "✓", user: "✗", auditor: "✗" }, + ].map((row) => ( + + + {[row.admin, row.manager, row.user, row.auditor].map((v, i) => ( + + ))} + + ))} + +
Endpointadminmanageruserauditor
{row.ep} + {v} +
+
+ +

Model Restrictions

+

+ Users with the user role can only access models listed in{" "} + rbac.user_allowed_models. Requests to other models are rejected with 403: +

+ + + + + The admin and manager roles have unrestricted model access —{" "} + user_allowed_models does not apply to them. + + +

Setting Up Roles in Keycloak

+

Assign roles to users in Keycloak:

+
    +
  1. Log in to Keycloak Admin Console (http://localhost:8080, admin/admin)
  2. +
  3. Go to Realm: veylantUsers
  4. +
  5. Select a user → Role MappingsRealm Roles
  6. +
  7. Assign one of: admin, manager, user, auditor
  8. +
+
+ ); +} diff --git a/web/src/pages/docs/guides/RoutingGuide.tsx b/web/src/pages/docs/guides/RoutingGuide.tsx new file mode 100644 index 0000000..bf30ee6 --- /dev/null +++ b/web/src/pages/docs/guides/RoutingGuide.tsx @@ -0,0 +1,146 @@ +import { CodeBlock } from "../components/CodeBlock"; +import { Callout } from "../components/Callout"; + +export function RoutingGuide() { + return ( +
+

Routing Rules Engine

+

+ The routing engine matches incoming AI requests against a prioritized list of rules and + dispatches them to the appropriate LLM provider. Rules are stored in PostgreSQL (JSONB + conditions) and cached in memory. +

+ +

Evaluation Order

+
    +
  • Rules are sorted ascending by priority (lower number = evaluated first)
  • +
  • First match wins — once a rule matches, evaluation stops
  • +
  • All conditions within a rule are AND-joined
  • +
  • An empty conditions array is a catch-all that matches everything
  • +
+ + + Use gaps between priorities (10, 20, 30, ...) so you can insert rules later without + renumbering. The catch-all rule should have the highest priority number (e.g. 999). + + +

Condition Fields & Operators

+
+ + + + + + + + + + {[ + { field: "user.role", ops: "eq, neq, in, nin", type: "string" }, + { field: "user.department", ops: "eq, neq, in, nin, contains", type: "string" }, + { field: "request.model", ops: "eq, neq, in, nin", type: "string (model ID)" }, + { field: "request.sensitivity", ops: "eq, neq, gte, lte", type: "low | medium | high" }, + { field: "request.use_case", ops: "eq, neq, contains, matches", type: "string" }, + { field: "request.token_estimate", ops: "gte, lte", type: "integer (token count)" }, + ].map((row) => ( + + + + + + ))} + +
FieldSupported OperatorsValue Type
{row.field}{row.ops}{row.type}
+
+ +

Example Rule Configurations

+ +

Route legal department to Anthropic

+ + +

Cost-optimize small requests

+ + +

HR department, high-sensitivity requests

+ + +

Catch-all fallback

+ + +

Fallback Chain

+

+ The fallback_providers array defines ordered fallbacks. The routing engine + tries providers in order: +

+
    +
  1. Check if primary provider's circuit breaker is open → skip if open
  2. +
  3. Try primary provider → if error, try next fallback
  4. +
  5. Continue through the fallback chain
  6. +
  7. If all providers fail → return 503
  8. +
+ +

Rule Caching

+

+ Rules are loaded from PostgreSQL and cached in memory. The cache is invalidated when a + rule is created, updated, or deleted via the admin API. You can also force a reload: +

+ + + + The in-memory rule cache refreshes every 60 seconds from PostgreSQL. Admin API changes + (create/update/delete) invalidate the cache immediately via a shared channel. + +
+ ); +} diff --git a/web/src/pages/docs/installation/ConfigurationPage.tsx b/web/src/pages/docs/installation/ConfigurationPage.tsx new file mode 100644 index 0000000..5846eee --- /dev/null +++ b/web/src/pages/docs/installation/ConfigurationPage.tsx @@ -0,0 +1,152 @@ +import { CodeBlock } from "../components/CodeBlock"; +import { Callout } from "../components/Callout"; +import { ParamTable } from "../components/ParamTable"; + +export function ConfigurationPage() { + return ( +
+

Configuration Reference

+

+ Veylant IA is configured via config.yaml at the repository root. Any key can + be overridden via an environment variable using the VEYLANT_ prefix and + replacing . with _. +

+ + + server.port: 9000VEYLANT_SERVER_PORT=9000 +
+ pii.timeout_ms: 200VEYLANT_PII_TIMEOUT_MS=200 +
+ +

Full config.yaml

+ + +

Server Settings

+ + +

PII Settings

+ + +

Production Checklist

+ + When server.env=production, these values must be set or the proxy fails to + start: + +
    +
  • + crypto.key — 32-byte AES-256 key (base64 encoded) +
  • +
  • + database.url — PostgreSQL DSN with TLS +
  • +
  • + keycloak.base_url — reachable Keycloak instance +
  • +
  • At least one provider API key
  • +
+ + +
+ ); +} diff --git a/web/src/pages/docs/installation/DockerComposePage.tsx b/web/src/pages/docs/installation/DockerComposePage.tsx new file mode 100644 index 0000000..1fc5ba4 --- /dev/null +++ b/web/src/pages/docs/installation/DockerComposePage.tsx @@ -0,0 +1,133 @@ +import { CodeBlock } from "../components/CodeBlock"; +import { Callout } from "../components/Callout"; + +export function DockerComposePage() { + return ( +
+

Docker Compose Setup

+

+ The recommended way to run Veylant IA locally or in a single-server staging environment is + Docker Compose. The full stack is defined in docker-compose.yml at the + repository root. +

+ +

Services

+
+ + + + + + + + + + + {[ + { service: "proxy", image: "Custom (Go, distroless)", port: "8090", purpose: "Main AI gateway" }, + { service: "pii", image: "Custom (Python FastAPI)", port: "8091 / 50051", purpose: "PII detection" }, + { service: "postgres", image: "postgres:16-alpine", port: "5432", purpose: "Config, users, policies" }, + { service: "redis", image: "redis:7-alpine", port: "6379", purpose: "Sessions, rate limits, PII maps" }, + { service: "clickhouse", image: "clickhouse:24.3-alpine", port: "8123 / 9000", purpose: "Audit logs & analytics" }, + { service: "keycloak", image: "keycloak:24.0", port: "8080", purpose: "IAM & SSO" }, + { service: "prometheus", image: "prom/prometheus:v2.53.0", port: "9090", purpose: "Metrics scraper" }, + { service: "grafana", image: "grafana:11.3.0", port: "3001", purpose: "Dashboards" }, + { service: "web", image: "node:20-alpine", port: "3000", purpose: "React dashboard" }, + ].map((row) => ( + + + + + + + ))} + +
ServiceImagePortPurpose
{row.service}{row.image}{row.port}{row.purpose}
+
+ +

Make Commands

+ + +

Startup Order & Health Checks

+

Services start in dependency order:

+
    +
  1. PostgreSQL → Redis → ClickHouse (databases)
  2. +
  3. Keycloak (waits for PostgreSQL health check)
  4. +
  5. PII service (independent)
  6. +
  7. Go proxy (waits for PostgreSQL, uses service_started for others)
  8. +
  9. React web (waits for proxy)
  10. +
  11. Prometheus → Grafana (monitoring)
  12. +
+ + + The proxy Docker image uses distroless/static — no shell, no{" "} + wget. Services that depend on the proxy use{" "} + condition: service_started rather than a health check command. + + +

First Run: Database Migrations

+

+ On first start, the proxy automatically applies PostgreSQL migrations (9 migration files) + and ClickHouse DDL. You can also run migrations manually: +

+ + +

Protocol Buffer Generation

+

+ If the gen/ or services/pii/gen/ directories are missing (e.g., + fresh clone), regenerate the gRPC stubs before starting: +

+ + + + The PII service starts but rejects all gRPC requests if services/pii/gen/ is + missing. Run make proto first. + + +

Viewing Logs

+ +
+ ); +} diff --git a/web/src/pages/docs/installation/ProvidersPage.tsx b/web/src/pages/docs/installation/ProvidersPage.tsx new file mode 100644 index 0000000..91bfce1 --- /dev/null +++ b/web/src/pages/docs/installation/ProvidersPage.tsx @@ -0,0 +1,116 @@ +import { CodeBlock } from "../components/CodeBlock"; +import { Callout } from "../components/Callout"; + +export function ProvidersPage() { + return ( +
+

Provider Setup

+

+ Veylant IA supports 5 LLM providers out of the box. Each provider implements the{" "} + provider.Adapter interface (Send, Stream,{" "} + Validate, HealthCheck). +

+ + + Implement the provider.Adapter interface in{" "} + internal/provider/<name>/ and register it in{" "} + cmd/proxy/main.go. No other changes needed. + + +

OpenAI

+ +

Supported models: gpt-4o, gpt-4o-mini, gpt-4-turbo, gpt-3.5-turbo

+ +

Anthropic

+ +

Supported models: claude-3-5-sonnet-20241022, claude-3-opus-20240229, claude-3-haiku-20240307

+ +

Azure OpenAI

+

+ Azure requires a deployment name instead of a model name. The proxy maps OpenAI model IDs + to Azure deployment names via the routing rule's target_model field. +

+ + +

Mistral AI

+ +

Supported models: mistral-large-latest, mistral-medium-latest, mistral-small-latest

+ +

Ollama (Self-hosted)

+

+ Ollama requires no API key. Ensure the Ollama server is reachable from the proxy container. +

+ + + + Ollama automatically uses GPU if available. For Docker, add{" "} + --gpus all to the container runtime command or use the{" "} + deploy.resources.reservations.devices key in docker-compose.yml. + + +

Check Provider Status

+

+ The admin API exposes circuit breaker state for all providers: +

+ + +

Circuit breaker states:

+
    +
  • closed — Normal operation, requests forwarded
  • +
  • open — Provider bypassed, fallback chain used
  • +
  • half-open — Testing if provider has recovered
  • +
+
+ ); +} diff --git a/web/src/pages/docs/nav.ts b/web/src/pages/docs/nav.ts new file mode 100644 index 0000000..8ca4bd0 --- /dev/null +++ b/web/src/pages/docs/nav.ts @@ -0,0 +1,88 @@ +export interface NavItem { + title: string; + path: string; +} + +export interface NavSection { + title: string; + items: NavItem[]; +} + +export const NAV_SECTIONS: NavSection[] = [ + { + title: "Getting Started", + items: [ + { title: "What is Veylant IA?", path: "/docs/getting-started/what-is-veylant" }, + { title: "Quick Start", path: "/docs/getting-started/quick-start" }, + { title: "Key Concepts", path: "/docs/getting-started/concepts" }, + ], + }, + { + title: "Installation", + items: [ + { title: "Docker Compose", path: "/docs/installation/docker" }, + { title: "Configuration", path: "/docs/installation/configuration" }, + { title: "Provider Setup", path: "/docs/installation/providers" }, + ], + }, + { + title: "API Reference", + items: [ + { title: "Authentication", path: "/docs/api/authentication" }, + { title: "Chat Completions", path: "/docs/api/chat-completions" }, + { title: "PII Analysis", path: "/docs/api/pii" }, + { title: "Admin — Policies", path: "/docs/api/admin/policies" }, + { title: "Admin — Users", path: "/docs/api/admin/users" }, + { title: "Admin — Audit Logs", path: "/docs/api/admin/logs" }, + { title: "Admin — Compliance", path: "/docs/api/admin/compliance" }, + { title: "Admin — Feature Flags", path: "/docs/api/admin/flags" }, + ], + }, + { + title: "Guides", + items: [ + { title: "PII Detection & Anonymization", path: "/docs/guides/pii" }, + { title: "Routing Rules Engine", path: "/docs/guides/routing" }, + { title: "RBAC & Permissions", path: "/docs/guides/rbac" }, + { title: "GDPR & AI Act Compliance", path: "/docs/guides/compliance" }, + { title: "Monitoring & Alerting", path: "/docs/guides/monitoring" }, + { title: "Circuit Breaker & Failover", path: "/docs/guides/circuit-breaker" }, + ], + }, + { + title: "Deployment", + items: [ + { title: "Docker Compose", path: "/docs/deployment/docker" }, + { title: "Kubernetes (Helm)", path: "/docs/deployment/kubernetes" }, + { title: "Blue/Green Deployment", path: "/docs/deployment/blue-green" }, + ], + }, + { + title: "Security", + items: [ + { title: "Security Model", path: "/docs/security/model" }, + { title: "API Key Management", path: "/docs/security/api-keys" }, + ], + }, + { + title: "Changelog", + items: [{ title: "v1.0.0 (Latest)", path: "/docs/changelog" }], + }, +]; + +/** Flatten all nav items into a list, preserving section context */ +export const ALL_NAV_ITEMS: (NavItem & { section: string })[] = NAV_SECTIONS.flatMap((s) => + s.items.map((item) => ({ ...item, section: s.title })) +); + +/** Get previous and next pages for pagination */ +export function getPrevNext(currentPath: string): { + prev: NavItem | null; + next: NavItem | null; +} { + const idx = ALL_NAV_ITEMS.findIndex((i) => i.path === currentPath); + return { + prev: idx > 0 ? ALL_NAV_ITEMS[idx - 1] : null, + next: idx < ALL_NAV_ITEMS.length - 1 ? ALL_NAV_ITEMS[idx + 1] : null, + }; +} diff --git a/web/src/pages/docs/security/ApiKeysPage.tsx b/web/src/pages/docs/security/ApiKeysPage.tsx new file mode 100644 index 0000000..6d30f26 --- /dev/null +++ b/web/src/pages/docs/security/ApiKeysPage.tsx @@ -0,0 +1,86 @@ +import { CodeBlock } from "../components/CodeBlock"; +import { Callout } from "../components/Callout"; + +export function ApiKeysPage() { + return ( +
+

API Key Management

+

+ Veylant IA never stores plain-text API keys. Provider keys (OpenAI, Anthropic, etc.) are + stored in HashiCorp Vault and rotated on a 90-day cycle. User-facing API keys use a + prefix+hash scheme. +

+ +

API Key Format

+

+ Veylant IA user API keys follow the format sk-vyl_{"{prefix}"}{"{hash}"}: +

+ +
    +
  • The display prefix (ab12cd34) is stored in plaintext for key identification in the dashboard
  • +
  • The full key is SHA-256 hashed; only the hash is stored in PostgreSQL
  • +
  • If a key is compromised, only the prefix reveals which key to revoke — not the key value
  • +
+ +

Provider API Keys

+ + Provider API keys (OpenAI, Anthropic, etc.) must never be committed to the repository. + Use environment variables or HashiCorp Vault. + + + + +

Key Rotation

+

Provider API keys are rotated on a 90-day cycle via Vault:

+
    +
  1. Generate new API key from the provider portal
  2. +
  3. Write new key to Vault: vault kv patch secret/veylant/providers openai_api_key=sk-new...
  4. +
  5. Vault agent syncs the new value to running pods automatically (no restart needed)
  6. +
  7. Revoke the old key from the provider portal
  8. +
+ +

Secret Detection in CI

+

+ Every commit is scanned by gitleaks for accidentally committed secrets. + Any string matching common API key patterns (starting with sk-,{" "} + sk-ant-, etc.) blocks the CI pipeline. +

+ + +

Key Usage Auditing

+

+ Every AI request records which API key (by prefix) was used in the ClickHouse audit log. + This allows you to trace the source of any request to a specific service or developer. +

+ +
+ ); +} diff --git a/web/src/pages/docs/security/SecurityModelPage.tsx b/web/src/pages/docs/security/SecurityModelPage.tsx new file mode 100644 index 0000000..2d4bcf2 --- /dev/null +++ b/web/src/pages/docs/security/SecurityModelPage.tsx @@ -0,0 +1,125 @@ +import { Callout } from "../components/Callout"; + +export function SecurityModelPage() { + return ( +
+

Security Model

+

+ Veylant IA is designed with a Zero Trust security model. Every component assumes the + network is hostile and authenticates and authorizes each request independently. +

+ +

Zero Trust Architecture

+
+ {[ + { + title: "mTLS Between Services", + desc: "All internal service-to-service communication uses mutual TLS. The proxy, PII service, PostgreSQL, Redis, and ClickHouse all authenticate each other via certificates.", + }, + { + title: "TLS 1.3 Externally", + desc: "External traffic uses TLS 1.3 minimum. TLS 1.0 and 1.1 are disabled at the Traefik/nginx gateway level.", + }, + { + title: "JWT Validation", + desc: "Every API request carries a signed JWT. The proxy validates the signature against Keycloak's JWKS endpoint on every request (cached with TTL).", + }, + { + title: "Network Policies", + desc: "Kubernetes NetworkPolicies restrict pod-to-pod communication. Only the proxy can reach the PII service; only Prometheus can scrape /metrics.", + }, + ].map((item) => ( +
+

{item.title}

+

{item.desc}

+
+ ))} +
+ +

Encryption at Rest

+
    +
  • + Prompt storage — Encrypted with AES-256-GCM using the{" "} + crypto.key config value (application-level, independent of disk encryption) +
  • +
  • + PII pseudonymization mappings — Encrypted in Redis with AES-256-GCM per + mapping entry +
  • +
  • + API keys — Stored as SHA-256 hashes only. The prefix (e.g.{" "} + sk-vyl_ab12cd34) is kept for display, but the full key is never stored +
  • +
  • + HashiCorp Vault — Provider API keys and the crypto key are stored in + Vault; 90-day rotation cycle +
  • +
+ +

Audit-of-the-Audit

+

+ All accesses to audit logs and compliance reports are themselves logged. This two-level + audit trail satisfies requirements for sensitive data access monitoring. +

+ +

Custom Security Rules (SAST)

+

+ CI enforces custom Semgrep rules (.semgrep.yml) that catch common security + issues specific to this codebase: +

+
+ + + + + + + + + {[ + { rule: "context-background-in-handler", catches: "context.Background() in HTTP handlers — use r.Context() to propagate cancellation" }, + { rule: "sql-string-concat", catches: "SQL string concatenation — use parameterized queries ($1, $2, ...)" }, + { rule: "sensitive-field-in-log", catches: "Logging password, api_key, token, secret, Authorization, email, prompt" }, + { rule: "hardcoded-api-key", catches: "String literals starting with sk- hardcoded in source" }, + { rule: "request-body-without-limit", catches: "json.NewDecoder(r.Body) without http.MaxBytesReader" }, + { rule: "python-eval-exec", catches: "eval() or exec() on variables in the PII service" }, + ].map((row) => ( + + + + + ))} + +
RuleWhat it catches
{row.rule}{row.catches}
+
+ +

Penetration Test Results (v1.0.0)

+ + Grey-box penetration test completed June 9–20, 2026. Results: +
    +
  • + Critical: 0 +
  • +
  • + High: 0 +
  • +
  • + Medium: 2 (remediated before launch) +
  • +
  • + Low: 3 (accepted risk, backlog) +
  • +
+ Full report available to enterprise customers under NDA. +
+ +

CI Security Pipeline

+
    +
  • Semgrep — SAST on every PR
  • +
  • Trivy — Container image scan (CRITICAL/HIGH blocking)
  • +
  • gitleaks — Secret detection in commits and history
  • +
  • OWASP ZAP — DAST (non-blocking, main branch only)
  • +
+
+ ); +} diff --git a/web/src/router.tsx b/web/src/router.tsx index 1c70936..5071d4b 100644 --- a/web/src/router.tsx +++ b/web/src/router.tsx @@ -15,6 +15,36 @@ import { CostsPage } from "@/pages/CostsPage"; import { CompliancePage } from "@/pages/CompliancePage"; import { NotFoundPage } from "@/pages/NotFoundPage"; +// Documentation site +import { DocLayout } from "@/pages/docs/DocLayout"; +import { DocsHomePage } from "@/pages/docs/DocsHomePage"; +import { WhatIsVeylantPage } from "@/pages/docs/getting-started/WhatIsVeylantPage"; +import { QuickStartPage } from "@/pages/docs/getting-started/QuickStartPage"; +import { KeyConceptsPage } from "@/pages/docs/getting-started/KeyConceptsPage"; +import { DockerComposePage } from "@/pages/docs/installation/DockerComposePage"; +import { ConfigurationPage } from "@/pages/docs/installation/ConfigurationPage"; +import { ProvidersPage as DocProvidersPage } from "@/pages/docs/installation/ProvidersPage"; +import { AuthenticationPage } from "@/pages/docs/api-reference/AuthenticationPage"; +import { ChatCompletionsPage } from "@/pages/docs/api-reference/ChatCompletionsPage"; +import { PiiAnalysisPage } from "@/pages/docs/api-reference/PiiAnalysisPage"; +import { AdminPoliciesPage } from "@/pages/docs/api-reference/AdminPoliciesPage"; +import { AdminUsersPage } from "@/pages/docs/api-reference/AdminUsersPage"; +import { AdminLogsPage } from "@/pages/docs/api-reference/AdminLogsPage"; +import { AdminCompliancePage } from "@/pages/docs/api-reference/AdminCompliancePage"; +import { AdminFlagsPage } from "@/pages/docs/api-reference/AdminFlagsPage"; +import { PiiGuide } from "@/pages/docs/guides/PiiGuide"; +import { RoutingGuide } from "@/pages/docs/guides/RoutingGuide"; +import { RbacGuide } from "@/pages/docs/guides/RbacGuide"; +import { ComplianceGuide } from "@/pages/docs/guides/ComplianceGuide"; +import { MonitoringGuide } from "@/pages/docs/guides/MonitoringGuide"; +import { CircuitBreakerGuide } from "@/pages/docs/guides/CircuitBreakerGuide"; +import { DockerPage } from "@/pages/docs/deployment/DockerPage"; +import { KubernetesPage } from "@/pages/docs/deployment/KubernetesPage"; +import { BlueGreenPage } from "@/pages/docs/deployment/BlueGreenPage"; +import { SecurityModelPage } from "@/pages/docs/security/SecurityModelPage"; +import { ApiKeysPage } from "@/pages/docs/security/ApiKeysPage"; +import { ChangelogPage } from "@/pages/docs/ChangelogPage"; + export const router = createBrowserRouter([ { path: "/", @@ -50,4 +80,45 @@ export const router = createBrowserRouter([ { path: "*", element: }, ], }, + // Documentation site — public, no auth required + { + path: "/docs", + element: , + children: [ + { index: true, element: }, + // Getting Started + { path: "getting-started/what-is-veylant", element: }, + { path: "getting-started/quick-start", element: }, + { path: "getting-started/concepts", element: }, + // Installation + { path: "installation/docker", element: }, + { path: "installation/configuration", element: }, + { path: "installation/providers", element: }, + // API Reference + { path: "api/authentication", element: }, + { path: "api/chat-completions", element: }, + { path: "api/pii", element: }, + { path: "api/admin/policies", element: }, + { path: "api/admin/users", element: }, + { path: "api/admin/logs", element: }, + { path: "api/admin/compliance", element: }, + { path: "api/admin/flags", element: }, + // Guides + { path: "guides/pii", element: }, + { path: "guides/routing", element: }, + { path: "guides/rbac", element: }, + { path: "guides/compliance", element: }, + { path: "guides/monitoring", element: }, + { path: "guides/circuit-breaker", element: }, + // Deployment + { path: "deployment/docker", element: }, + { path: "deployment/kubernetes", element: }, + { path: "deployment/blue-green", element: }, + // Security + { path: "security/model", element: }, + { path: "security/api-keys", element: }, + // Changelog + { path: "changelog", element: }, + ], + }, ]); diff --git a/web/tailwind.config.ts b/web/tailwind.config.ts index 325503c..ddf37bc 100644 --- a/web/tailwind.config.ts +++ b/web/tailwind.config.ts @@ -54,7 +54,7 @@ const config: Config = { }, }, }, - plugins: [], + plugins: [require("@tailwindcss/typography")], }; export default config;