diff --git a/contracts/epistemics/freshness.go b/contracts/epistemics/freshness.go new file mode 100644 index 0000000..b2f49a5 --- /dev/null +++ b/contracts/epistemics/freshness.go @@ -0,0 +1,90 @@ +package epistemics + +import ( + "fmt" + "git.cin.su/m/Assistant/contracts/world" +) + +type VolatilityMode string + +const ( + VolatilityImmutable VolatilityMode = "IMMUTABLE" + VolatilityVersionBound VolatilityMode = "VERSION_BOUND" + VolatilityVerySlow VolatilityMode = "VERY_SLOW" + VolatilitySlow VolatilityMode = "SLOW" + VolatilityMedium VolatilityMode = "MEDIUM" + VolatilityFast VolatilityMode = "FAST" + VolatilityEphemeral VolatilityMode = "EPHEMERAL" + VolatilityCustom VolatilityMode = "CUSTOM" +) + +type TemporalApplicability struct { + ObservedAtUnixMillis int64 + ValidFromUnixMillis *int64 + ValidUntilUnixMillis *int64 + LastVerifiedAtUnixMillis *int64 + Volatility VolatilitySpec + VersionBindings []world.VersionBinding +} +type VolatilitySpec struct { + Mode VolatilityMode + ExpectedChangeIntervalMillis *int64 + RevalidateAfterMillis *int64 + HardStaleAfterMillis *int64 + InvalidateOnVersionChange bool + RevalidationPolicyRef string +} +type FreshnessAssessmentState string + +const ( + FreshnessAssessmentCurrent FreshnessAssessmentState = "CURRENT" + FreshnessAssessmentAcceptable FreshnessAssessmentState = "ACCEPTABLE" + FreshnessAssessmentAging FreshnessAssessmentState = "AGING" + FreshnessAssessmentStale FreshnessAssessmentState = "STALE" + FreshnessAssessmentVersionMismatch FreshnessAssessmentState = "VERSION_MISMATCH" + FreshnessAssessmentHistoricalOnly FreshnessAssessmentState = "HISTORICAL_ONLY" + FreshnessAssessmentUnknown FreshnessAssessmentState = "UNKNOWN" +) + +type FreshnessAssessment struct { + SourceKnowledgeRef string + TaskRef string + State FreshnessAssessmentState + Reason string + AgeMillis int64 + VersionMatchState string + RevalidationAttemptRef string + AssessedAtUnixMillis int64 +} +type RevalidationRequest struct { + ID string + SourceKnowledgeRef string + TaskRef string + RequiredVersionRefs []world.SemanticReference + RequestedAtUnixMillis int64 +} +type RevalidationResponse struct { + RequestID string + Assessment FreshnessAssessment + NewEvidenceRefs []EvidenceRef + CompletedAtUnixMillis int64 +} + +func (v VolatilitySpec) Validate() error { + if v.Mode == "" { + return fmt.Errorf("volatility mode is required") + } + return nil +} +func (t TemporalApplicability) Validate() error { + if t.ObservedAtUnixMillis < 1 { + return fmt.Errorf("observed time is required") + } + return t.Volatility.Validate() +} +func (a FreshnessAssessment) Validate() error { + if a.SourceKnowledgeRef == "" || a.TaskRef == "" || a.State == "" || a.AgeMillis < 0 || a.AssessedAtUnixMillis < 1 { + return fmt.Errorf("freshness assessment is incomplete") + } + return nil +} diff --git a/contracts/work/freshness.go b/contracts/work/freshness.go new file mode 100644 index 0000000..8c107b8 --- /dev/null +++ b/contracts/work/freshness.go @@ -0,0 +1,28 @@ +package work + +import "git.cin.su/m/Assistant/contracts/world" + +type FreshnessRequirementMode string +type FreshnessFallbackPolicy string + +const ( + FreshnessAny FreshnessRequirementMode = "ANY" + FreshnessPreferCurrent FreshnessRequirementMode = "PREFER_CURRENT" + FreshnessRequireCurrent FreshnessRequirementMode = "REQUIRE_CURRENT" + FreshnessRequireWithinAge FreshnessRequirementMode = "REQUIRE_WITHIN_AGE" + FreshnessRequireVersionMatch FreshnessRequirementMode = "REQUIRE_VERSION_MATCH" + FreshnessLiveRequired FreshnessRequirementMode = "LIVE_REQUIRED" + FallbackAllowStaleDisclosure FreshnessFallbackPolicy = "ALLOW_STALE_WITH_DISCLOSURE" + FallbackHypothesisOnly FreshnessFallbackPolicy = "USE_AS_HYPOTHESIS_ONLY" + FallbackWaitForSource FreshnessFallbackPolicy = "WAIT_FOR_SOURCE" + FallbackAskOwner FreshnessFallbackPolicy = "ASK_OWNER" + FallbackStopUnknown FreshnessFallbackPolicy = "STOP_WITH_UNKNOWN" +) + +type TaskFreshnessRequirement struct { + Mode FreshnessRequirementMode + MaximumAgeMillis *int64 + RequiredVersionRefs []world.SemanticReference + RevalidationRequired bool + FallbackPolicy FreshnessFallbackPolicy +} diff --git a/dev/ACTIVE_TASK.json b/dev/ACTIVE_TASK.json index 1aeb56a..95631c0 100644 --- a/dev/ACTIVE_TASK.json +++ b/dev/ACTIVE_TASK.json @@ -1 +1 @@ -{"task_file":"dev/tasks/GEN2-006-normalize-epistemics-evidence-propositions-claims-and-belief-assessments.json","task_id":"GEN2-006","task_revision":1} \ No newline at end of file +{"task_file":"dev/tasks/GEN2-007-general-freshness-volatility-temporal-applicability-and-revalidation.json","task_id":"GEN2-007","task_revision":1} \ No newline at end of file diff --git a/modules/epistemics/domain/freshness.go b/modules/epistemics/domain/freshness.go new file mode 100644 index 0000000..6a65253 --- /dev/null +++ b/modules/epistemics/domain/freshness.go @@ -0,0 +1,30 @@ +package domain + +import "git.cin.su/m/Assistant/contracts/epistemics" + +// AssessFreshness receives the Work-owned requirement as primitive policy +// inputs, keeping the Epistemics owner independent of Work package types. +func AssessFreshness(applicability epistemics.TemporalApplicability, mode string, maximumAgeMillis *int64, nowMillis int64) epistemics.FreshnessAssessment { + age := nowMillis - applicability.ObservedAtUnixMillis + if age < 0 { + age = 0 + } + state := epistemics.FreshnessAssessmentCurrent + reason := "observation is within applicability window" + if applicability.ValidUntilUnixMillis != nil && nowMillis > *applicability.ValidUntilUnixMillis { + state, reason = epistemics.FreshnessAssessmentStale, "valid-until elapsed" + } + if applicability.Volatility.HardStaleAfterMillis != nil && age > *applicability.Volatility.HardStaleAfterMillis { + state, reason = epistemics.FreshnessAssessmentStale, "hard volatility window elapsed" + } + if applicability.Volatility.RevalidateAfterMillis != nil && age > *applicability.Volatility.RevalidateAfterMillis && state == epistemics.FreshnessAssessmentCurrent { + state, reason = epistemics.FreshnessAssessmentAging, "revalidation window elapsed" + } + if maximumAgeMillis != nil && age > *maximumAgeMillis { + state, reason = epistemics.FreshnessAssessmentStale, "task maximum age exceeded" + } + if mode == "LIVE_REQUIRED" { + state, reason = epistemics.FreshnessAssessmentHistoricalOnly, "live source required" + } + return epistemics.FreshnessAssessment{SourceKnowledgeRef: "knowledge", TaskRef: "task", State: state, Reason: reason, AgeMillis: age, AssessedAtUnixMillis: nowMillis} +} diff --git a/modules/epistemics/domain/freshness_test.go b/modules/epistemics/domain/freshness_test.go new file mode 100644 index 0000000..ec05af9 --- /dev/null +++ b/modules/epistemics/domain/freshness_test.go @@ -0,0 +1,17 @@ +package domain_test + +import ( + "git.cin.su/m/Assistant/contracts/epistemics" + "git.cin.su/m/Assistant/modules/epistemics/domain" + "testing" +) + +func TestFreshnessIsTaskRelative(t *testing.T) { + hard := int64(100) + app := epistemics.TemporalApplicability{ObservedAtUnixMillis: 1, Volatility: epistemics.VolatilitySpec{Mode: epistemics.VolatilityVersionBound, HardStaleAfterMillis: &hard}} + current := domain.AssessFreshness(app, "ANY", nil, 50) + historical := domain.AssessFreshness(app, "LIVE_REQUIRED", nil, 50) + if current.State != epistemics.FreshnessAssessmentCurrent || historical.State != epistemics.FreshnessAssessmentHistoricalOnly { + t.Fatalf("got %v and %v", current.State, historical.State) + } +}