JuliusBrussee/caveman · error

session-value artifact outside validity window

Error message

session-value artifact outside validity window

What it means

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.

Source

Thrown at proxy/routing/session_value.go:181

func ValidateSessionValueArtifact(artifact SessionValuePolicyArtifact, organizationID, projectID, candidatePoolHash string, now time.Time) error {
	if artifact.Schema != SessionValueArtifactSchema || artifact.StateSchemaVersion != SessionValueStateSchema {
		return errors.New("session-value artifact schema mismatch")
	}
	if strings.TrimSpace(artifact.OrganizationID) == "" || artifact.OrganizationID != strings.TrimSpace(organizationID) ||
		strings.TrimSpace(artifact.ProjectID) == "" || artifact.ProjectID != strings.TrimSpace(projectID) {
		return errors.New("session-value artifact tenant scope mismatch")
	}
	if artifact.PolicyVersion <= 0 || artifact.RouterVersion != SessionValueRouterVersion || strings.TrimSpace(artifact.EstimatorVersion) == "" {
		return errors.New("session-value artifact version identity invalid")
	}
	if !validCompactPoolHash(artifact.CandidatePoolHash) || artifact.CandidatePoolHash != candidatePoolHash {
		return errors.New("session-value artifact candidate pool mismatch")
	}
	if !validSHA256Ref(artifact.TrainingManifestHash) || strings.TrimSpace(artifact.TrainingExtractor) == "" || strings.TrimSpace(artifact.OutcomeContractVersion) == "" {
		return errors.New("session-value artifact training lineage invalid")
	}
	if artifact.ValidFrom.IsZero() || artifact.ValidUntil.IsZero() || !artifact.ValidUntil.After(artifact.ValidFrom) || now.Before(artifact.ValidFrom) || !now.Before(artifact.ValidUntil) {
		return errors.New("session-value artifact outside validity window")
	}
	if artifact.RollbackParentHash != "" && (!validSHA256Ref(artifact.RollbackParentHash) || artifact.RollbackParentHash == artifact.ArtifactHash) {
		return errors.New("session-value artifact rollback lineage invalid")
	}
	if !finite(artifact.QualityUncertaintyZ) || artifact.QualityUncertaintyZ <= 0 || artifact.QualityUncertaintyZ > 5 ||
		!finite(artifact.MaxInversePropensity) || artifact.MaxInversePropensity < 1 || artifact.MaxInversePropensity > 100 {
		return errors.New("session-value artifact confidence policy invalid")
	}
	featureNames := SessionValueFeatureNames()
	if len(artifact.FeatureSpecs) != len(featureNames) || len(artifact.Actions) == 0 {
		return errors.New("session-value artifact has no features or actions")
	}
	for i, spec := range artifact.FeatureSpecs {
		if spec.Name != featureNames[i] || (i > 0 && artifact.FeatureSpecs[i-1].Name >= spec.Name) {
			return errors.New("session-value artifact feature vocabulary or order invalid")
		}
		if spec.Name == "turn_index" && !spec.Required {
			return errors.New("session-value artifact must require turn_index")

View on GitHub (pinned to 766dce6b13)

Solutions

  1. Issue and deploy a fresh artifact whose window covers the current time before loading it.
  2. Set the validity window generously relative to your deploy cadence (days, not minutes) and always in UTC.
  3. If 'now' looks wrong, check the validating host's clock (NTP status) and confirm the trainer writes RFC3339 UTC timestamps.

Example fix

// before
{
  "valid_from": "2026-08-01T00:00:00Z",
  "valid_until": "2026-08-02T00:00:00Z"  // loaded on 2026-08-17: expired
}

// after
{
  "valid_from": "2026-08-17T00:00:00Z",
  "valid_until": "2026-08-24T00:00:00Z"  // reissued window covering deploy
}
Defensive patterns

Strategy: validation

Validate before calling

// Before validation: check the window with the same clock semantics.
now := time.Now().UTC()
if artifact.ValidFrom.IsZero() || artifact.ValidUntil.IsZero() ||
	!artifact.ValidUntil.After(artifact.ValidFrom) ||
	now.Before(artifact.ValidFrom) || !now.Before(artifact.ValidUntil) {
	return fmt.Errorf("artifact window %s..%s does not cover %s; issue a fresh artifact",
		artifact.ValidFrom.Format(time.RFC3339), artifact.ValidUntil.Format(time.RFC3339), now.Format(time.RFC3339))
}

Type guard

func artifactInWindow(a routing.SessionValuePolicyArtifact, now time.Time) bool {
	return !a.ValidFrom.IsZero() && !a.ValidUntil.IsZero() &&
		a.ValidUntil.After(a.ValidFrom) && !now.Before(a.ValidFrom) && now.Before(a.ValidUntil)
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@766dce6b13 (2026-08-18). Data as JSON: /api/errors/92de1178c239ce89. Report an issue: GitHub.