package domain import ( "errors" "strings" "git.cin.su/m/Assistant/contracts/capability" ) var ErrInvalidSkillProfile = errors.New("invalid skill profile") var ErrInvalidFastPathInput = errors.New("invalid fast path input") func ValidateSkillProfile(value capability.SkillProfile) error { if value.ID == "" || value.Revision < 1 || strings.TrimSpace(value.Name) == "" || strings.TrimSpace(value.Description) == "" || value.Status == "" || value.ConfidenceMillis < 0 || value.ConfidenceMillis > 1000 || value.RecordedAtUnixMillis < 1 { return ErrInvalidSkillProfile } switch value.Status { case capability.SkillCandidate, capability.SkillEvaluating, capability.SkillValidated, capability.SkillDegraded, capability.SkillSuspended, capability.SkillRetired: default: return ErrInvalidSkillProfile } return nil } func ObserveSkillOutcome(profile capability.SkillProfile, observation capability.SkillOutcomeObservation) (capability.SkillProfile, error) { if err := ValidateSkillProfile(profile); err != nil { return capability.SkillProfile{}, err } if observation.ExperienceRef == "" || observation.ObservedAtUnixMillis < 1 { return capability.SkillProfile{}, ErrInvalidSkillProfile } next := profile next.Revision++ next.RecordedAtUnixMillis = observation.ObservedAtUnixMillis switch observation.Outcome { case capability.UsageSucceeded: next.SupportingExperienceRefs = appendEvidenceRef(next.SupportingExperienceRefs, observation.ExperienceRef) if next.Status == capability.SkillCandidate || next.Status == capability.SkillEvaluating { next.Status = capability.SkillValidated } next.LastValidatedUnixMillis = observation.ObservedAtUnixMillis next.ConfidenceMillis = clampConfidence(next.ConfidenceMillis + 25) case capability.UsageFailed: next.KnownFailureExperienceRefs = appendEvidenceRef(next.KnownFailureExperienceRefs, observation.ExperienceRef) next.ObservedFailureCount++ next.ConfidenceMillis = clampConfidence(next.ConfidenceMillis - 150) case capability.UsageUnknown, capability.UsageSkipped: // UNKNOWN and SKIPPED are evidence of incomplete observation or a safe // non-application, not evidence that the skill itself failed. default: return capability.SkillProfile{}, ErrInvalidSkillProfile } if observation.UserCorrected { next.ObservedCorrectionCount++ next.KnownFailureExperienceRefs = appendEvidenceRef(next.KnownFailureExperienceRefs, observation.ExperienceRef) next.ConfidenceMillis = clampConfidence(next.ConfidenceMillis - 200) } if next.Status == capability.SkillValidated && (next.ObservedFailureCount >= 3 || next.ObservedCorrectionCount >= 2 || next.ConfidenceMillis < 500) { next.Status = capability.SkillDegraded } if next.Status == capability.SkillDegraded && next.ConfidenceMillis < 250 { next.Status = capability.SkillSuspended } return next, nil } func DecideFastPath(profile capability.SkillProfile, supported bool, confidenceMillis int64, environmentVersionChanged bool, conflict bool, observedAtUnixMillis int64) (capability.FastPathDecision, error) { if err := ValidateSkillProfile(profile); err != nil { return capability.FastPathDecision{}, err } if confidenceMillis < 0 || confidenceMillis > 1000 || observedAtUnixMillis < 1 { return capability.FastPathDecision{}, ErrInvalidFastPathInput } decision := capability.FastPathDecision{ SkillProfileID: profile.ID, Decision: capability.FastPathApplied, Explanation: "validated fast path applied", ObservedAtUnixMillis: observedAtUnixMillis, } switch { case profile.Status == capability.SkillSuspended || profile.Status == capability.SkillRetired: return decline(decision, capability.FastPathStale, "skill profile is not eligible for fast-path use"), nil case environmentVersionChanged: return decline(decision, capability.FastPathStale, "environment or implementation version changed since validation"), nil case conflict: return decline(decision, capability.FastPathConflict, "input conflicts with known fast-path assumptions"), nil case !supported: return decline(decision, capability.FastPathOutOfDistribution, "input is outside supported semantics"), nil case confidenceMillis < 700 || profile.ConfidenceMillis < 700: return decline(decision, capability.FastPathLowConfidence, "confidence is too low for optimized path"), nil case profile.Status != capability.SkillValidated: return decline(decision, capability.FastPathNotApplicable, "skill is not validated"), nil default: return decision, nil } } func decline(decision capability.FastPathDecision, reason capability.FastPathDeclineReason, explanation string) capability.FastPathDecision { decision.Decision = capability.FastPathEscalate decision.DeclineReason = reason decision.RequiresCognition = true decision.Explanation = explanation return decision } func appendEvidenceRef(values []capability.SkillEvidenceRef, ref capability.SkillEvidenceRef) []capability.SkillEvidenceRef { for _, value := range values { if value == ref { return values } } return append(values, ref) } func clampConfidence(value int64) int64 { if value < 0 { return 0 } if value > 1000 { return 1000 } return value }