{"record":{"id":"8370908e17f9e108","repo":"JuliusBrussee/caveman","slug":"session-value-artifact-confidence-policy-invalid","errorCode":null,"errorMessage":"session-value artifact confidence policy invalid","messagePattern":"session-value artifact confidence policy invalid","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"proxy/routing/session_value.go","lineNumber":188,"sourceCode":"\t}\n\tif artifact.PolicyVersion <= 0 || artifact.RouterVersion != SessionValueRouterVersion || strings.TrimSpace(artifact.EstimatorVersion) == \"\" {\n\t\treturn errors.New(\"session-value artifact version identity invalid\")\n\t}\n\tif !validCompactPoolHash(artifact.CandidatePoolHash) || artifact.CandidatePoolHash != candidatePoolHash {\n\t\treturn errors.New(\"session-value artifact candidate pool mismatch\")\n\t}\n\tif !validSHA256Ref(artifact.TrainingManifestHash) || strings.TrimSpace(artifact.TrainingExtractor) == \"\" || strings.TrimSpace(artifact.OutcomeContractVersion) == \"\" {\n\t\treturn errors.New(\"session-value artifact training lineage invalid\")\n\t}\n\tif artifact.ValidFrom.IsZero() || artifact.ValidUntil.IsZero() || !artifact.ValidUntil.After(artifact.ValidFrom) || now.Before(artifact.ValidFrom) || !now.Before(artifact.ValidUntil) {\n\t\treturn errors.New(\"session-value artifact outside validity window\")\n\t}\n\tif artifact.RollbackParentHash != \"\" && (!validSHA256Ref(artifact.RollbackParentHash) || artifact.RollbackParentHash == artifact.ArtifactHash) {\n\t\treturn errors.New(\"session-value artifact rollback lineage invalid\")\n\t}\n\tif !finite(artifact.QualityUncertaintyZ) || artifact.QualityUncertaintyZ <= 0 || artifact.QualityUncertaintyZ > 5 ||\n\t\t!finite(artifact.MaxInversePropensity) || artifact.MaxInversePropensity < 1 || artifact.MaxInversePropensity > 100 {\n\t\treturn errors.New(\"session-value artifact confidence policy invalid\")\n\t}\n\tfeatureNames := SessionValueFeatureNames()\n\tif len(artifact.FeatureSpecs) != len(featureNames) || len(artifact.Actions) == 0 {\n\t\treturn errors.New(\"session-value artifact has no features or actions\")\n\t}\n\tfor i, spec := range artifact.FeatureSpecs {\n\t\tif spec.Name != featureNames[i] || (i > 0 && artifact.FeatureSpecs[i-1].Name >= spec.Name) {\n\t\t\treturn errors.New(\"session-value artifact feature vocabulary or order invalid\")\n\t\t}\n\t\tif spec.Name == \"turn_index\" && !spec.Required {\n\t\t\treturn errors.New(\"session-value artifact must require turn_index\")\n\t\t}\n\t\tif !finite(spec.Mean) || !finite(spec.Scale) || spec.Scale <= 0 || !finite(spec.Min) || !finite(spec.Max) || spec.Min < 0 || spec.Max < spec.Min {\n\t\t\treturn fmt.Errorf(\"session-value feature %q bounds invalid\", spec.Name)\n\t\t}\n\t}\n\tseenActions := map[string]struct{}{}\n\tartifactPool := make([]Candidate, 0, len(artifact.Actions))","sourceCodeStart":170,"sourceCodeEnd":206,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/766dce6b1394ebb56a3090748d5a0240a5aefb36/proxy/routing/session_value.go#L170-L206","documentation":"Confidence-policy constants are range-checked: QualityUncertaintyZ must be finite and in (0, 5] (the z-score used for conservative quality estimates) and MaxInversePropensity must be finite and in [1, 100] (the importance-weighting cap). NaN, infinities, or out-of-range values would make exploration corrections unbounded or degenerate, so they are rejected.","triggerScenarios":"The trainer emitting NaN or Inf into the JSON (division by zero during estimation, empty-data covariance); QualityUncertaintyZ set to 0 or negative; MaxInversePropensity set to 0 (weights everything out) or above 100 (explodes estimator variance).","commonSituations":"Numerical instability in offline training runs; struct defaults never overwritten by the fitting step; downstream JSON handling turning null/garbage into non-finite floats.","solutions":["Clamp and validate both constants in the trainer: 0 < QualityUncertaintyZ <= 5 and 1 <= MaxInversePropensity <= 100.","Fail the training job when any artifact float is non-finite (math.IsNaN / math.IsInf) instead of emitting it.","Regenerate and re-seal the artifact after fixing the estimation code."],"exampleFix":"// before\n{\n  \"quality_uncertainty_z\": 0,\n  \"max_inverse_propensity\": 1000\n}\n\n// after\n{\n  \"quality_uncertainty_z\": 1.96,\n  \"max_inverse_propensity\": 20\n}","handlingStrategy":"validation","validationCode":"// Before validation: reject non-finite and out-of-range confidence constants.\nfunc finite(v float64) bool { return !math.IsNaN(v) && !math.IsInf(v, 0) }\nif !finite(artifact.QualityUncertaintyZ) || artifact.QualityUncertaintyZ <= 0 || artifact.QualityUncertaintyZ > 5 ||\n\t!finite(artifact.MaxInversePropensity) || artifact.MaxInversePropensity < 1 || artifact.MaxInversePropensity > 100 {\n\treturn errors.New(\"confidence policy out of range: need 0 < z <= 5 and 1 <= max_inverse_propensity <= 100\")\n}","typeGuard":"func confidencePolicySane(a routing.SessionValuePolicyArtifact) bool {\n\treturn finite(a.QualityUncertaintyZ) && a.QualityUncertaintyZ > 0 && a.QualityUncertaintyZ <= 5 &&\n\t\tfinite(a.MaxInversePropensity) && a.MaxInversePropensity >= 1 && a.MaxInversePropensity <= 100\n}","tryCatchPattern":null,"preventionTips":["Fail training when any emitted float is NaN or Inf (math.IsNaN/math.IsInf sweep before serialization).","Validate confidence constants against the (0,5] and [1,100] ranges in trainer unit tests.","Never hand-edit these constants in shipped artifacts; regenerate from the trainer."],"tags":["routing","numeric-bounds","nan","estimator","artifact"],"backgroundTag":"parameter-out-of-range","analyzedSha":"766dce6b1394ebb56a3090748d5a0240a5aefb36","analyzedAt":"2026-08-18T03:14:35.516Z","contentChangedAt":"2026-08-18T03:14:35.516Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}