Skip to content

Commit fa59d09

Browse files
committed
Validate LiteLLM config candidates
1 parent 764df31 commit fa59d09

9 files changed

Lines changed: 438 additions & 20 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
- Added Forgejo migration candidate validation for the GitHub Enterprise assessment path.
3131
- Added identity realm/client candidate validation for the Okta/Auth0 assessment path.
3232
- Added edge VCL, HAProxy, and Coraza candidate validation for the Cloudflare/Akamai assessment path.
33+
- Added LiteLLM/vLLM config candidate validation for the OpenAI/Anthropic assessment path.
3334
- Updated CI and release workflows to Node.js 24-native GitHub Actions.
3435

3536
## 0.1.0 - 2026-05-24

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ Included in the current implementation:
116116
- OpenTelemetry Collector sketch.
117117
- ArgoCD starter manifest.
118118
- Typed migration plan manifest and phase-gate Markdown plan.
119-
- Validation report with embedded JSON Schema checks, Grafana dashboard, Prometheus alert, OpenTelemetry collector, ArgoCD, Forgejo migration, identity realm/client, and edge VCL/HAProxy/Coraza candidate checks, YAML/JSON parsing, evidence ref checks, secret scan, and optional `promtool`/`kubeconform` checks.
119+
- Validation report with embedded JSON Schema checks, Grafana dashboard, Prometheus alert, OpenTelemetry collector, ArgoCD, Forgejo migration, identity realm/client, edge VCL/HAProxy/Coraza, and LiteLLM/vLLM candidate checks, YAML/JSON parsing, evidence ref checks, secret scan, and optional `promtool`/`kubeconform` checks.
120120
- Evidence bundle export.
121121
- Evidence bundle path-safety checks that reject symlinks in exported project sections.
122122
- No-op assist provider and explicit opt-in LiteLLM assist.
@@ -164,7 +164,7 @@ Okta/Auth0 to Keycloak/Zitadel collects applications, SAML/OIDC client metadata,
164164

165165
Cloudflare/Akamai to Varnish/HAProxy/Coraza collects DNS records, WAF rules, cache rules, redirects, origins, TLS settings, bot rules, and page rules from live Cloudflare/Akamai APIs or local fixtures. The Akamai collector uses read-only EdgeGrid-authenticated calls for Edge DNS, Property Manager, and optional AppSec metadata. It generates validated VCL, HAProxy, Coraza, cache parity, and WAF enforcement review artifacts.
166166

167-
OpenAI/Anthropic to vLLM/LiteLLM collects model usage classes, token volumes, latency expectations, sensitive prompt categories, tool usage, and fallback behavior from local fixtures. It can also collect model-grouped aggregate OpenAI completions usage, aggregate Anthropic Messages API usage, available model metadata where exposed, server web-search tool metadata, and hourly peak estimates from live provider APIs without storing prompts or credentials. The path generates self-hosted LLM readiness, LiteLLM routing, vLLM sizing, evaluation, and data sensitivity artifacts.
167+
OpenAI/Anthropic to vLLM/LiteLLM collects model usage classes, token volumes, latency expectations, sensitive prompt categories, tool usage, and fallback behavior from local fixtures. It can also collect model-grouped aggregate OpenAI completions usage, aggregate Anthropic Messages API usage, available model metadata where exposed, server web-search tool metadata, and hourly peak estimates from live provider APIs without storing prompts or credentials. The path generates self-hosted LLM readiness, validated LiteLLM routing, vLLM sizing, evaluation, and data sensitivity artifacts.
168168

169169
## License
170170

docs/architecture.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@ Okta/Auth0 to Keycloak/Zitadel reuses the same normalized inventory and evidence
1414

1515
Cloudflare/Akamai to Varnish/HAProxy/Coraza generates and validates edge-specific VCL, HAProxy, and Coraza candidate files plus cache and WAF review reports. It supports fixture import plus read-only live Cloudflare and Akamai collectors.
1616

17-
OpenAI/Anthropic to vLLM/LiteLLM generates AI provider readiness, LiteLLM routing, vLLM sizing, evaluation, and data sensitivity artifacts. It supports fixture import plus read-only live OpenAI/Anthropic aggregate usage collectors.
17+
OpenAI/Anthropic to vLLM/LiteLLM generates AI provider readiness, validated LiteLLM routing, vLLM sizing, evaluation, and data sensitivity artifacts. It supports fixture import plus read-only live OpenAI/Anthropic aggregate usage collectors.
1818

1919
AI assist is optional and never required for validation or export.

docs/cli.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@ The CI test suite also runs this definition-of-done pipeline against the fixture
2727

2828
The checked-in Datadog example can be refreshed with `make example VERSION=0.1.0-dev`. CI also runs `make example-smoke` as part of `make verify` to ensure the example fixture still completes the full pipeline.
2929

30-
`openexit validate` performs typed consistency checks, embedded JSON Schema validation, Grafana dashboard candidate validation, Prometheus alert-rule candidate validation, OpenTelemetry collector candidate validation, ArgoCD candidate validation, Forgejo migration candidate validation, identity realm/client candidate validation, edge VCL/HAProxy/Coraza candidate validation, YAML/JSON parse checks, evidence reference checks, secret scanning, and optional external tool checks when `promtool` or `kubeconform` are installed.
30+
`openexit validate` performs typed consistency checks, embedded JSON Schema validation, Grafana dashboard candidate validation, Prometheus alert-rule candidate validation, OpenTelemetry collector candidate validation, ArgoCD candidate validation, Forgejo migration candidate validation, identity realm/client candidate validation, edge VCL/HAProxy/Coraza candidate validation, LiteLLM/vLLM candidate validation, YAML/JSON parse checks, evidence reference checks, secret scanning, and optional external tool checks when `promtool` or `kubeconform` are installed.
3131

3232
`openexit export` refuses to package symlinks from exported project sections, even with `--force`, so evidence bundles cannot accidentally include files from outside the project tree.
3333

34-
Generate individual artifacts with `openexit generate --artifact <name>`. The primary Datadog path supports `mapping`, `assessment`, `risk-register`, `manual-review`, `cost-drivers`, `target-architecture`, `acceptance-criteria`, `rollback-plan`, `runbook`, `restore-drill-checklist`, `alert-shadowing-plan`, `migration-plan`, `grafana-dashboards`, `prometheus-rules`, `opentelemetry`, and `argocd`. The GitHub Enterprise path also supports `forgejo-migration-candidate`; the identity path also supports `realm-client-candidate`; the edge path also supports `vcl-candidates`, `haproxy-candidates`, and `coraza-rule-candidates`.
34+
Generate individual artifacts with `openexit generate --artifact <name>`. The primary Datadog path supports `mapping`, `assessment`, `risk-register`, `manual-review`, `cost-drivers`, `target-architecture`, `acceptance-criteria`, `rollback-plan`, `runbook`, `restore-drill-checklist`, `alert-shadowing-plan`, `migration-plan`, `grafana-dashboards`, `prometheus-rules`, `opentelemetry`, and `argocd`. The GitHub Enterprise path also supports `forgejo-migration-candidate`; the identity path also supports `realm-client-candidate`; the edge path also supports `vcl-candidates`, `haproxy-candidates`, and `coraza-rule-candidates`; the AI provider path also supports `litellm-config-candidate`.
3535

3636
The GitHub Enterprise to Forgejo fixture path uses local JSON metadata:
3737

docs/openai-anthropic-to-vllm-litellm.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,4 +106,6 @@ openexit validate --project ./anthropic-live
106106
- `assessment/data-sensitivity-report.md`
107107
- `generated-config/ai/litellm/config.candidate.yaml`
108108

109+
Validation checks the LiteLLM candidate for source/target metadata, non-production safety flags, absence of provider credentials, explicit LiteLLM/vLLM router policy, one route per captured usage class, preserved source models, owners, latency and fallback metadata, runtime credential placeholders, vLLM sizing assumptions, and evidence refs.
110+
109111
All generated target files are candidates and require human review, model evaluation, load testing, data-control review, and tool-policy review before production use.

docs/release.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Release-blocking v0.1 requirements:
1616
- Markdown handover artifacts.
1717
- Grafana, Prometheus, OpenTelemetry, and ArgoCD candidate generators.
1818
- Typed migration plan manifest with assessment, pilot, shadow, and cutover phase gates.
19-
- Validation engine with embedded JSON Schema checks, Grafana dashboard, Prometheus alert, OpenTelemetry collector, ArgoCD, Forgejo migration, identity realm/client, and edge VCL/HAProxy/Coraza candidate checks, YAML/JSON parsing, evidence refs, secret scan, optional promtool, and optional kubeconform.
19+
- Validation engine with embedded JSON Schema checks, Grafana dashboard, Prometheus alert, OpenTelemetry collector, ArgoCD, Forgejo migration, identity realm/client, edge VCL/HAProxy/Coraza, and LiteLLM/vLLM candidate checks, YAML/JSON parsing, evidence refs, secret scan, optional promtool, and optional kubeconform.
2020
- Evidence bundle export with checksums and OpenExit version metadata.
2121
- No-op AI assist and optional external assist behind explicit opt-in.
2222
- Documentation, examples, CI, release draft workflow, and reproducible release artifacts.
@@ -54,6 +54,7 @@ The AI provider path is complete for local fixture assessment workflows and incl
5454
- [ ] `validation/validation-report.md` includes `forgejo-migration-candidate: passed` for the GitHub Enterprise to Forgejo pipeline.
5555
- [ ] `validation/validation-report.md` includes `identity-realm-client-candidate: passed` for the Okta/Auth0 to Keycloak/Zitadel pipeline.
5656
- [ ] `validation/validation-report.md` includes `edge-candidates: passed` for the Cloudflare/Akamai to Varnish/HAProxy/Coraza pipeline.
57+
- [ ] `validation/validation-report.md` includes `litellm-config-candidate: passed` for the OpenAI/Anthropic to vLLM/LiteLLM pipeline.
5758
- [ ] `CHANGELOG.md` has a `0.1.0` section.
5859
- [ ] Exported bundle README includes version, commit, build date, bundle timestamp, and candidate warning.
5960
- [ ] Export refuses symlinks in exported project sections, including when `--force` is used.

internal/app/e2e_test.go

Lines changed: 79 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -720,20 +720,7 @@ func TestValidationRejectsBrokenEdgeHAProxyCandidate(t *testing.T) {
720720

721721
func TestAIProviderFixturePipeline(t *testing.T) {
722722
projectDir := filepath.Join(t.TempDir(), "ai-demo")
723-
fixturePath := filepath.Join("..", "..", "testdata", "ai-provider", "small.json")
724-
725-
commands := [][]string{
726-
{"init", projectDir, "--source", "ai-provider", "--target", "vllm-litellm"},
727-
{"collect", "ai-fixture", "--project", projectDir, "--input", fixturePath},
728-
{"assess", "--project", projectDir, "--target", "vllm-litellm"},
729-
{"generate", "--project", projectDir, "--all"},
730-
{"validate", "--project", projectDir},
731-
}
732-
for _, args := range commands {
733-
if err := executeForTest(args...); err != nil {
734-
t.Fatalf("openexit %s failed: %v", strings.Join(args, " "), err)
735-
}
736-
}
723+
runAIProviderFixturePipeline(t, projectDir)
737724
for _, rel := range []string{
738725
"inventory/openexit.inventory.yaml",
739726
"mapping/openexit.mapping.yaml",
@@ -770,6 +757,67 @@ func TestAIProviderFixturePipeline(t *testing.T) {
770757
t.Fatalf("expected assessment finding %s", id)
771758
}
772759
}
760+
report, err := os.ReadFile(filepath.Join(projectDir, "validation", "validation-report.md"))
761+
if err != nil {
762+
t.Fatal(err)
763+
}
764+
if !strings.Contains(string(report), "litellm-config-candidate: passed") {
765+
t.Fatalf("expected LiteLLM candidate validation marker, got:\n%s", string(report))
766+
}
767+
}
768+
769+
func TestValidationRejectsProductionReadyLiteLLMCandidate(t *testing.T) {
770+
projectDir := filepath.Join(t.TempDir(), "ai-demo")
771+
runAIProviderFixturePipeline(t, projectDir)
772+
candidatePath := filepath.Join(projectDir, "generated-config", "ai", "litellm", "config.candidate.yaml")
773+
data, err := os.ReadFile(candidatePath)
774+
if err != nil {
775+
t.Fatal(err)
776+
}
777+
corrupt := strings.Replace(string(data), "productionReady: false", "productionReady: true", 1)
778+
if corrupt == string(data) {
779+
t.Fatal("test fixture did not contain productionReady marker")
780+
}
781+
if err := os.WriteFile(candidatePath, []byte(corrupt), 0o644); err != nil {
782+
t.Fatal(err)
783+
}
784+
if err := executeForTest("validate", "--project", projectDir); err == nil {
785+
t.Fatal("expected validation to fail when LiteLLM candidate is marked production ready")
786+
}
787+
report, err := os.ReadFile(filepath.Join(projectDir, "validation", "validation-report.md"))
788+
if err != nil {
789+
t.Fatal(err)
790+
}
791+
if !strings.Contains(string(report), "litellm-config-candidate") || !strings.Contains(string(report), "productionReady") {
792+
t.Fatalf("expected LiteLLM candidate productionReady failure in validation report, got:\n%s", string(report))
793+
}
794+
}
795+
796+
func TestValidationRejectsBrokenLiteLLMRouteCandidate(t *testing.T) {
797+
projectDir := filepath.Join(t.TempDir(), "ai-demo")
798+
runAIProviderFixturePipeline(t, projectDir)
799+
candidatePath := filepath.Join(projectDir, "generated-config", "ai", "litellm", "config.candidate.yaml")
800+
data, err := os.ReadFile(candidatePath)
801+
if err != nil {
802+
t.Fatal(err)
803+
}
804+
corrupt := strings.Replace(string(data), "model: vllm/support-chat-replacement-for-gpt-4o", "model: vllm/support-chat-unreviewed", 1)
805+
if corrupt == string(data) {
806+
t.Fatal("test fixture did not contain LiteLLM model route")
807+
}
808+
if err := os.WriteFile(candidatePath, []byte(corrupt), 0o644); err != nil {
809+
t.Fatal(err)
810+
}
811+
if err := executeForTest("validate", "--project", projectDir); err == nil {
812+
t.Fatal("expected validation to fail when LiteLLM route drifts from inventory")
813+
}
814+
report, err := os.ReadFile(filepath.Join(projectDir, "validation", "validation-report.md"))
815+
if err != nil {
816+
t.Fatal(err)
817+
}
818+
if !strings.Contains(string(report), "litellm-config-candidate") || !strings.Contains(string(report), "litellm_params") {
819+
t.Fatalf("expected LiteLLM route failure in validation report, got:\n%s", string(report))
820+
}
773821
}
774822

775823
func TestExportRefusesInvalidProjectWithoutForce(t *testing.T) {
@@ -838,6 +886,23 @@ func runEdgeFixturePipeline(t *testing.T, projectDir string) {
838886
}
839887
}
840888

889+
func runAIProviderFixturePipeline(t *testing.T, projectDir string) {
890+
t.Helper()
891+
fixturePath := filepath.Join("..", "..", "testdata", "ai-provider", "small.json")
892+
commands := [][]string{
893+
{"init", projectDir, "--source", "ai-provider", "--target", "vllm-litellm"},
894+
{"collect", "ai-fixture", "--project", projectDir, "--input", fixturePath},
895+
{"assess", "--project", projectDir, "--target", "vllm-litellm"},
896+
{"generate", "--project", projectDir, "--all"},
897+
{"validate", "--project", projectDir},
898+
}
899+
for _, args := range commands {
900+
if err := executeForTest(args...); err != nil {
901+
t.Fatalf("openexit %s failed: %v", strings.Join(args, " "), err)
902+
}
903+
}
904+
}
905+
841906
func executeForTest(args ...string) error {
842907
cmd := NewRootCommand()
843908
cmd.SetArgs(args)

0 commit comments

Comments
 (0)