feat: add configurable medium model fleet slot #141

Merged
m merged 1 commits from codex/model-fleet-readiness into main 2026-08-22 18:11:24 +00:00
7 changed files with 109 additions and 10 deletions
+49 -8
View File
@@ -45,7 +45,7 @@ func main() {
discoveries := map[string]providerRegistration{}
var builtinLocal *providerRegistration
externalRecovery := []providerRegistration{}
cpuAPIKey, err := loadLocalCPUProviderAPIKey()
cpuAPIKey, err := loadProviderAPIKey("LOCAL_CPU_PROVIDER_API_KEY", "LOCAL_CPU_PROVIDER_API_KEY_FILE", "LOCAL_CPU_PROVIDER_CREDENTIAL_REF", "credential://domovoy/local-cpu/provider-api-key/v1")
if err != nil {
log.Fatal(err)
}
@@ -62,6 +62,27 @@ func main() {
cpu := providerRegistration{ProviderInstanceID: "local-cpu-primary", Kind: "openai-compatible-chat-v1", EndpointRef: "local-cpu-primary-v1", CredentialRef: cpuCredentialRef, AuthMode: cpuAuthMode, BillingMode: capability.BillingFree, DataPolicy: capability.DataPolicyLocalOnly, Provider: cpuAdapter, Discovery: cpuAdapter}
providers[cpu.Kind] = append(providers[cpu.Kind], cpu)
discoveries["cpu"] = cpu
mediumURL := strings.TrimSpace(os.Getenv("LOCAL_MEDIUM_PROVIDER_URL"))
mediumAPIKey := ""
if mediumURL != "" {
mediumAPIKey, err = loadProviderAPIKey("LOCAL_MEDIUM_PROVIDER_API_KEY", "LOCAL_MEDIUM_PROVIDER_API_KEY_FILE", "LOCAL_MEDIUM_PROVIDER_CREDENTIAL_REF", "credential://domovoy/local-medium/provider-api-key/v1")
if err != nil {
log.Fatal(err)
}
mediumAdapter, adapterErr := models.NewOpenAICompatibleLocalAdapterWithAPIKey(strings.TrimRight(mediumURL, "/"), mediumAPIKey, nil)
if adapterErr != nil {
log.Fatal(adapterErr)
}
mediumAuthMode := capability.AuthNone
mediumCredentialRef := capability.CredentialRef("")
if mediumAPIKey != "" {
mediumAuthMode = capability.AuthKey
mediumCredentialRef = capability.CredentialRef(envOr("LOCAL_MEDIUM_PROVIDER_CREDENTIAL_REF", "credential://domovoy/local-medium/provider-api-key/v1"))
}
medium := providerRegistration{ProviderInstanceID: "local-medium-primary", Kind: "openai-compatible-chat-v1", EndpointRef: "local-medium-primary-v1", CredentialRef: mediumCredentialRef, AuthMode: mediumAuthMode, BillingMode: capability.BillingFree, DataPolicy: capability.DataPolicyLocalOnly, Provider: mediumAdapter, Discovery: mediumAdapter}
providers[medium.Kind] = append(providers[medium.Kind], medium)
discoveries["medium"] = medium
}
if lifeboat, enabled, lifeboatErr := newLifeboatProvider(os.Getenv("LOCAL_LIFEBOAT_PROVIDER_URL")); lifeboatErr != nil {
log.Fatal(lifeboatErr)
} else if enabled {
@@ -86,6 +107,7 @@ func main() {
mux.Handle("/healthz", health.Handler{Runtime: "cognitive-worker", SemanticGeneration: 1, ArchitectureGeneration: 1})
readinessClient := &http.Client{Timeout: 2 * time.Second}
cpuReadinessClient := &http.Client{Timeout: 10 * time.Second}
mediumReadinessClient := &http.Client{Timeout: 10 * time.Second}
lifeboatURL := strings.TrimSuffix(strings.TrimRight(envOr("LOCAL_LIFEBOAT_PROVIDER_URL", "http://cognitive-lifeboat:8086/v1"), "/"), "/v1") + "/health"
fleetClient := &http.Client{Timeout: 2 * time.Second}
preferredFleet := capability.FleetTier(envOr("COGNITIVE_PREFERRED_FLEET_TIER", string(capability.FleetTierHeavy)))
@@ -96,16 +118,28 @@ func main() {
cpuEndpoint := strings.TrimRight(envOr("LOCAL_CPU_PROVIDER_URL", "http://docker.cin.su:18082/v1"), "/") + "/models"
cpuHealthProbe := health.OptionalHTTPProbeWithHeaders(cpuReadinessClient, cpuEndpoint, cpuFleetHeaders)
cpuHealthProbe.Timeout = 10 * time.Second
mediumEndpoint := ""
mediumHeaders := map[string]string{}
mediumHealthProbe := health.Probe{}
if mediumURL != "" {
mediumEndpoint = strings.TrimRight(mediumURL, "/") + "/models"
if mediumAPIKey != "" {
mediumHeaders["Authorization"] = "Bearer " + mediumAPIKey
}
mediumHealthProbe = health.OptionalHTTPProbeWithHeaders(mediumReadinessClient, mediumEndpoint, mediumHeaders)
mediumHealthProbe.Timeout = 10 * time.Second
}
readinessProbes := []health.Probe{
health.HTTPProbe(readinessClient, strings.TrimRight(envOr("CORE_RUNTIME_URL", "http://core-runtime:8080"), "/")+"/healthz"),
cpuHealthProbe,
mediumHealthProbe,
health.HTTPProbe(readinessClient, lifeboatURL),
health.OptionalHTTPProbe(readinessClient, strings.TrimRight(envOr("LOCAL_GPU_PROVIDER_URL", "http://192.168.220.91:11434"), "/")+"/api/tags"),
}
mux.Handle("/readyz", health.ReadinessHandler{Runtime: "cognitive-worker", Probes: readinessProbes})
mux.Handle("/v1/fleet/readiness", fleetReadinessHandler(fleetClient, preferredFleet, []fleetEndpoint{
{ProviderID: "local-cpu-primary", ModelID: "local-cpu-model", Tier: capability.FleetTierFast, URL: cpuEndpoint, Headers: cpuFleetHeaders, Client: &http.Client{Timeout: 10 * time.Second}},
{ProviderID: "local-medium-unconfigured", ModelID: "medium-unconfigured", Tier: capability.FleetTierMedium},
{ProviderID: "local-medium-primary", ModelID: "local-medium-model", Tier: capability.FleetTierMedium, URL: mediumEndpoint, Headers: mediumHeaders, Client: mediumReadinessClient},
{ProviderID: "local-gpu-opportunistic", ModelID: "gpu-opportunistic", Tier: capability.FleetTierHeavy, URL: strings.TrimRight(envOr("LOCAL_GPU_PROVIDER_URL", "http://192.168.220.91:11434"), "/") + "/api/tags"},
{ProviderID: "cognitive-lifeboat", ModelID: "qwen3-8b-lifeboat", Tier: capability.FleetTierLifeboat, URL: lifeboatURL},
}))
@@ -166,17 +200,20 @@ func refreshProviderDiscoveries(resolver bindingResolver, service discoveryServi
}
}
func loadLocalCPUProviderAPIKey() (string, error) {
if _, set := os.LookupEnv("LOCAL_CPU_PROVIDER_API_KEY"); set {
return "", fmt.Errorf("LOCAL_CPU_PROVIDER_API_KEY direct environment secret is forbidden; use LOCAL_CPU_PROVIDER_API_KEY_FILE")
func loadProviderAPIKey(directEnvName, fileEnvName, refEnvName, defaultRef string) (string, error) {
if _, set := os.LookupEnv(directEnvName); set {
return "", fmt.Errorf("%s direct environment secret is forbidden; use %s", directEnvName, fileEnvName)
}
fileName := strings.TrimSpace(os.Getenv("LOCAL_CPU_PROVIDER_API_KEY_FILE"))
fileName := strings.TrimSpace(os.Getenv(fileEnvName))
if fileName == "" {
return "", nil
}
ref := strings.TrimSpace(os.Getenv("LOCAL_CPU_PROVIDER_CREDENTIAL_REF"))
ref := strings.TrimSpace(os.Getenv(refEnvName))
if ref == "" {
return "", fmt.Errorf("LOCAL_CPU_PROVIDER_CREDENTIAL_REF is required when LOCAL_CPU_PROVIDER_API_KEY_FILE is configured")
ref = defaultRef
}
if ref == "" {
return "", fmt.Errorf("%s is required when %s is configured", refEnvName, fileEnvName)
}
resolver, err := platformsecrets.NewFileResolver(envOr("SECRET_BACKEND_ROOT", "/run/secrets"))
if err != nil {
@@ -185,6 +222,10 @@ func loadLocalCPUProviderAPIKey() (string, error) {
return resolver.ResolveFile(secretcontracts.SecretFileRef{CredentialRef: secretcontracts.CredentialRef(ref), Purpose: secretcontracts.SecretPurposeProviderAPIKey, Consumer: secretcontracts.RuntimeConsumerCognitive, Path: fileName})
}
func loadLocalCPUProviderAPIKey() (string, error) {
return loadProviderAPIKey("LOCAL_CPU_PROVIDER_API_KEY", "LOCAL_CPU_PROVIDER_API_KEY_FILE", "LOCAL_CPU_PROVIDER_CREDENTIAL_REF", "credential://domovoy/local-cpu/provider-api-key/v1")
}
func deterministicAdvisory(w http.ResponseWriter, r *http.Request) {
request, ok := decodeAdvisoryRequest(w, r)
if !ok {
+25
View File
@@ -43,3 +43,28 @@ func TestLoadLocalCPUProviderAPIKeyRejectsDirectEnvironmentSecret(t *testing.T)
t.Fatal("expected direct environment secret to be rejected")
}
}
func TestLoadProviderAPIKeySupportsMediumCredentialRef(t *testing.T) {
t.Setenv("LOCAL_MEDIUM_PROVIDER_API_KEY", "")
_ = os.Unsetenv("LOCAL_MEDIUM_PROVIDER_API_KEY")
root := t.TempDir()
path := filepath.Join(root, "medium_provider_api_key")
if err := os.WriteFile(path, []byte("medium-key\n"), 0o600); err != nil {
t.Fatal(err)
}
t.Setenv("LOCAL_MEDIUM_PROVIDER_API_KEY_FILE", path)
t.Setenv("LOCAL_MEDIUM_PROVIDER_CREDENTIAL_REF", "credential://domovoy/local-medium/provider-api-key/v1")
t.Setenv("SECRET_BACKEND_ROOT", root)
key, err := loadProviderAPIKey("LOCAL_MEDIUM_PROVIDER_API_KEY", "LOCAL_MEDIUM_PROVIDER_API_KEY_FILE", "LOCAL_MEDIUM_PROVIDER_CREDENTIAL_REF", "credential://domovoy/local-medium/provider-api-key/v1")
if err != nil || key != "medium-key" {
t.Fatalf("unexpected medium key resolution result: err=%v", err)
}
}
func TestLoadProviderAPIKeyRejectsMediumDirectEnvironmentSecret(t *testing.T) {
t.Setenv("LOCAL_MEDIUM_PROVIDER_API_KEY", "must-not-be-used")
t.Setenv("LOCAL_MEDIUM_PROVIDER_API_KEY_FILE", "")
if _, err := loadProviderAPIKey("LOCAL_MEDIUM_PROVIDER_API_KEY", "LOCAL_MEDIUM_PROVIDER_API_KEY_FILE", "LOCAL_MEDIUM_PROVIDER_CREDENTIAL_REF", "credential://domovoy/local-medium/provider-api-key/v1"); err == nil {
t.Fatal("expected direct medium environment secret to be rejected")
}
}
+3
View File
@@ -214,6 +214,9 @@ services:
LOCAL_CPU_PROVIDER_URL: http://docker.cin.su:18082/v1
LOCAL_CPU_PROVIDER_API_KEY_FILE: ${LOCAL_CPU_PROVIDER_API_KEY_FILE:-}
LOCAL_CPU_PROVIDER_CREDENTIAL_REF: ${LOCAL_CPU_PROVIDER_CREDENTIAL_REF:-credential://domovoy/local-cpu/provider-api-key/v1}
LOCAL_MEDIUM_PROVIDER_URL: ${LOCAL_MEDIUM_PROVIDER_URL:-}
LOCAL_MEDIUM_PROVIDER_API_KEY_FILE: ${LOCAL_MEDIUM_PROVIDER_API_KEY_FILE:-}
LOCAL_MEDIUM_PROVIDER_CREDENTIAL_REF: ${LOCAL_MEDIUM_PROVIDER_CREDENTIAL_REF:-credential://domovoy/local-medium/provider-api-key/v1}
LOCAL_LIFEBOAT_PROVIDER_URL: http://cognitive-lifeboat:8086/v1
LOCAL_GPU_PROVIDER_URL: http://192.168.220.91:11434
COGNITIVE_OWNER_PRINCIPAL_ID: domovoy-creator-principal
+2 -2
View File
@@ -1,5 +1,5 @@
{
"task_file": "dev/tasks/DEV-000167-cpu-readiness-client-timeout.json",
"task_id": "DEV-000167",
"task_file": "dev/tasks/DEV-000168-configurable-medium-fleet-slot.json",
"task_id": "DEV-000168",
"task_revision": 1
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000168",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "205d8f620603ccf3e0138f6beb93156450664314",
"risk_tier": "E",
"intent": "Make the MEDIUM model-fleet slot a real optional OpenAI-compatible provider path with protected file credentials, discovery, readiness and gateway registration.",
"requirement_ids": ["WP-200", "WP-301", "Model fleet/failover"],
"allowed_change_scope": {"modules": ["cognitive-worker", "deployment-policy", "tests"], "paths": ["cmd/cognitive-worker/main.go", "cmd/cognitive-worker/main_test.go", "deploy/docker-compose.prod.yml", "tests/cognitive_advisory/cognitive_worker_env_policy.ps1", "tests/cognitive_advisory/cognitive_worker_env_policy_test.ps1", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000168-configurable-medium-fleet-slot.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "database/**", "contracts/**", "adapters/**", ".gitea/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 1, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["existing provider adapter contract", "existing discovery registration", "existing fleet readiness contract", "existing CredentialRef file contract"], "may_change": []},
"semantic_owner": "cognitive-worker/capability registration boundary",
"canonical_state": [],
"state_transitions": ["MEDIUM endpoint absent -> NOT_CONFIGURED", "MEDIUM endpoint configured -> discovery/readiness registration", "available provider -> eligible gateway binding subject to core registry policy"],
"side_effects_and_privileges": ["declares one new deployment configuration surface for an existing read-only secret volume; provider key is resolved only through the existing protected file CredentialRef"],
"persistence_and_migrations": ["none"],
"failure_retry_restart": ["medium probe has a bounded 10-second client timeout; absent endpoint remains NOT_CONFIGURED; fallback selection remains Lifeboat when preferred tiers are unavailable"],
"assumptions": ["A real medium OpenAI-compatible runtime may be supplied later through deployment environment injection", "No production medium runtime is claimed by this change"],
"acceptance_scenarios": ["empty medium URL produces NOT_CONFIGURED without startup failure", "configured medium URL is registered for discovery and fleet readiness", "direct medium secret environment is rejected", "medium CredentialRef file is accepted", "CPU/GPU/Lifeboat behavior remains unchanged"],
"required_tests": ["go test ./...", "go vet ./...", "preflight", "check-diff", "check-architecture", "independent", "git diff --check", "environment policy tests", "CI", "production fleet readiness"],
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_CONFLICT"]
}
@@ -3,6 +3,11 @@ Set-StrictMode -Version Latest
$script:AllowedCognitiveWorkerEnvironment = @{
'CORE_RUNTIME_URL' = 'http_url'
'LOCAL_CPU_PROVIDER_URL' = 'http_url'
'LOCAL_CPU_PROVIDER_API_KEY_FILE' = 'secret_file'
'LOCAL_CPU_PROVIDER_CREDENTIAL_REF' = 'nonempty'
'LOCAL_MEDIUM_PROVIDER_URL' = 'http_url'
'LOCAL_MEDIUM_PROVIDER_API_KEY_FILE' = 'secret_file'
'LOCAL_MEDIUM_PROVIDER_CREDENTIAL_REF' = 'nonempty'
'LOCAL_GPU_PROVIDER_URL' = 'http_url'
'LOCAL_OLLAMA_PROVIDER_URL' = 'http_url'
'COGNITIVE_OWNER_PRINCIPAL_ID' = 'nonempty'
@@ -15,6 +15,8 @@ function Assert-PolicyFail([string]$Name, [string[]]$Environment) {
}
Assert-PolicyPass 'CPU provider endpoint' @('LOCAL_CPU_PROVIDER_URL=http://docker.cin.su:18082/v1')
Assert-PolicyPass 'optional medium provider endpoint' @('LOCAL_MEDIUM_PROVIDER_URL=http://medium.example/v1')
Assert-PolicyPass 'approved medium CredentialRef file' @('LOCAL_MEDIUM_PROVIDER_API_KEY_FILE=/run/secrets/medium_api_key', 'LOCAL_MEDIUM_PROVIDER_CREDENTIAL_REF=credential://domovoy/local-medium/provider-api-key/v1')
Assert-PolicyPass 'Ollama provider endpoint' @('LOCAL_OLLAMA_PROVIDER_URL=http://192.168.220.91:11434')
Assert-PolicyFail 'plaintext OpenAI API key' @('OPENAI_API_KEY=secret')
Assert-PolicyFail 'plaintext provider token' @('PROVIDER_TOKEN=secret')