{"record":{"id":"92de1178c239ce89","repo":"JuliusBrussee/caveman","slug":"session-value-artifact-outside-validity-window","errorCode":null,"errorMessage":"session-value artifact outside validity window","messagePattern":"session-value artifact outside validity window","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"proxy/routing/session_value.go","lineNumber":181,"sourceCode":"func ValidateSessionValueArtifact(artifact SessionValuePolicyArtifact, organizationID, projectID, candidatePoolHash string, now time.Time) error {\n\tif artifact.Schema != SessionValueArtifactSchema || artifact.StateSchemaVersion != SessionValueStateSchema {\n\t\treturn errors.New(\"session-value artifact schema mismatch\")\n\t}\n\tif strings.TrimSpace(artifact.OrganizationID) == \"\" || artifact.OrganizationID != strings.TrimSpace(organizationID) ||\n\t\tstrings.TrimSpace(artifact.ProjectID) == \"\" || artifact.ProjectID != strings.TrimSpace(projectID) {\n\t\treturn errors.New(\"session-value artifact tenant scope mismatch\")\n\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\")","sourceCodeStart":163,"sourceCodeEnd":199,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/766dce6b1394ebb56a3090748d5a0240a5aefb36/proxy/routing/session_value.go#L163-L199","documentation":"Session-value artifacts are time-boxed: ValidFrom and ValidUntil must both be non-zero, ValidUntil must be strictly after ValidFrom, and the validation time must satisfy ValidFrom <= now < ValidUntil. Outside that window the policy is expired or not yet active, and the router refuses it so no stale policy is served.","triggerScenarios":"Loading an expired artifact (now >= ValidUntil) or one whose window has not started yet (now < ValidFrom); zero timestamps from an unstamped generator; an inverted window from a timezone bug; clock skew between the host that sealed the artifact and the host validating it.","commonSituations":"Router restarted hours or days after artifact issuance when the validity window is short; trainer serializing local-time timestamps instead of UTC RFC3339; NTP drift on the validating host.","solutions":["Issue and deploy a fresh artifact whose window covers the current time before loading it.","Set the validity window generously relative to your deploy cadence (days, not minutes) and always in UTC.","If 'now' looks wrong, check the validating host's clock (NTP status) and confirm the trainer writes RFC3339 UTC timestamps."],"exampleFix":"// before\n{\n  \"valid_from\": \"2026-08-01T00:00:00Z\",\n  \"valid_until\": \"2026-08-02T00:00:00Z\"  // loaded on 2026-08-17: expired\n}\n\n// after\n{\n  \"valid_from\": \"2026-08-17T00:00:00Z\",\n  \"valid_until\": \"2026-08-24T00:00:00Z\"  // reissued window covering deploy\n}","handlingStrategy":"validation","validationCode":"// Before validation: check the window with the same clock semantics.\nnow := time.Now().UTC()\nif artifact.ValidFrom.IsZero() || artifact.ValidUntil.IsZero() ||\n\t!artifact.ValidUntil.After(artifact.ValidFrom) ||\n\tnow.Before(artifact.ValidFrom) || !now.Before(artifact.ValidUntil) {\n\treturn fmt.Errorf(\"artifact window %s..%s does not cover %s; issue a fresh artifact\",\n\t\tartifact.ValidFrom.Format(time.RFC3339), artifact.ValidUntil.Format(time.RFC3339), now.Format(time.RFC3339))\n}","typeGuard":"func artifactInWindow(a routing.SessionValuePolicyArtifact, now time.Time) bool {\n\treturn !a.ValidFrom.IsZero() && !a.ValidUntil.IsZero() &&\n\t\ta.ValidUntil.After(a.ValidFrom) && !now.Before(a.ValidFrom) && now.Before(a.ValidUntil)\n}","tryCatchPattern":null,"preventionTips":["Issue artifacts with windows sized to your deploy cadence (days), always in UTC RFC3339.","Automate reissue before expiry instead of relying on operators noticing expiry at load time.","Keep validating hosts NTP-synced; skew across the fleet can flip this check."],"tags":["routing","validity-window","time","clock-skew","artifact"],"backgroundTag":"policy-validity-window-expired","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"}