193 lines
8.2 KiB
Go
193 lines
8.2 KiB
Go
package domain
|
|
|
|
import (
|
|
"errors"
|
|
"slices"
|
|
"sort"
|
|
"strings"
|
|
|
|
"git.cin.su/m/Assistant/contracts/capability"
|
|
"git.cin.su/m/Assistant/platform/ids"
|
|
)
|
|
|
|
var ErrInvalidCapabilityImplementation = errors.New("invalid capability implementation")
|
|
var ErrInvalidCapabilityRequirement = errors.New("invalid capability requirement")
|
|
var ErrNoCapabilityImplementation = errors.New("no eligible capability implementation")
|
|
|
|
func ValidateCapabilityImplementation(value capability.CapabilityImplementation) error {
|
|
if value.ID == "" || value.CapabilityID == "" || value.CapabilityKind == "" || value.OwnerPrincipalID == "" ||
|
|
value.Revision < 1 || value.Kind == "" || value.TargetRef == "" || value.AuthorityPolicyRef == "" ||
|
|
value.ObservedAtUnixMillis < 1 || !validQualification(value.Qualification) || !validActivation(value.Activation) ||
|
|
!validHealth(value.Health) {
|
|
return ErrInvalidCapabilityImplementation
|
|
}
|
|
switch value.Kind {
|
|
case capability.ImplementationInternalFunction, capability.ImplementationCognitiveBinding, capability.ImplementationExternalAdapter, capability.ImplementationAgent:
|
|
default:
|
|
return ErrInvalidCapabilityImplementation
|
|
}
|
|
for _, constraint := range value.DataConstraints {
|
|
switch constraint {
|
|
case capability.DataConstraintLocalOnly, capability.DataConstraintExternalAllowed, capability.DataConstraintNoCredentialForward:
|
|
default:
|
|
return ErrInvalidCapabilityImplementation
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ResolveCapability(requirement capability.CapabilityResolutionRequirement, candidates []capability.CapabilityImplementation) (capability.CapabilityResolution, error) {
|
|
if requirement.CapabilityKind == "" || requirement.OwnerPrincipalID == "" || requirement.OriginalIntentID == "" || requirement.SourceUtteranceID == "" || requirement.ObservedAtUnixMillis < 1 {
|
|
return capability.CapabilityResolution{}, ErrInvalidCapabilityRequirement
|
|
}
|
|
resolutionID, err := ids.New()
|
|
if err != nil {
|
|
return capability.CapabilityResolution{}, err
|
|
}
|
|
resolution := capability.CapabilityResolution{
|
|
ID: capability.CapabilityResolutionID(resolutionID),
|
|
Requirement: requirement,
|
|
Status: capability.ResolutionUnavailable,
|
|
ExcludedReasons: map[capability.CapabilityImplementationID]capability.GapReason{},
|
|
ObservedAtUnixMillis: requirement.ObservedAtUnixMillis,
|
|
}
|
|
eligible := make([]capability.CapabilityImplementation, 0, len(candidates))
|
|
anyForCapability := false
|
|
dominantReason := capability.GapNoImplementation
|
|
for _, candidate := range candidates {
|
|
if candidate.CapabilityKind != requirement.CapabilityKind {
|
|
continue
|
|
}
|
|
if requirement.CapabilityID != "" && candidate.CapabilityID != requirement.CapabilityID {
|
|
continue
|
|
}
|
|
anyForCapability = true
|
|
reason := exclusionReason(requirement, candidate)
|
|
if reason != "" {
|
|
resolution.ExcludedReasons[candidate.ID] = reason
|
|
if dominantReason == capability.GapNoImplementation {
|
|
dominantReason = reason
|
|
}
|
|
continue
|
|
}
|
|
eligible = append(eligible, candidate)
|
|
}
|
|
if len(eligible) == 0 {
|
|
if !anyForCapability {
|
|
dominantReason = capability.GapNoImplementation
|
|
}
|
|
gap, err := BuildCapabilityGap(requirement, dominantReason, gapDetail(dominantReason), requirement.ObservedAtUnixMillis)
|
|
if err != nil {
|
|
return capability.CapabilityResolution{}, err
|
|
}
|
|
resolution.Gap = &gap
|
|
return resolution, ErrNoCapabilityImplementation
|
|
}
|
|
sort.SliceStable(eligible, func(i, j int) bool { return betterImplementation(eligible[i], eligible[j]) })
|
|
resolution.Status = capability.ResolutionAvailable
|
|
resolution.Eligible = make([]capability.ResolvedCapabilityImplementation, 0, len(eligible))
|
|
for index, item := range eligible {
|
|
resolution.Eligible = append(resolution.Eligible, capability.ResolvedCapabilityImplementation{Implementation: item, Selected: index == 0, Rank: index + 1})
|
|
}
|
|
return resolution, nil
|
|
}
|
|
|
|
func BuildCapabilityGap(requirement capability.CapabilityResolutionRequirement, reason capability.GapReason, detail string, millis int64) (capability.CapabilityGap, error) {
|
|
id, err := ids.New()
|
|
if err != nil {
|
|
return capability.CapabilityGap{}, err
|
|
}
|
|
return capability.CapabilityGap{
|
|
ID: capability.CapabilityGapID(id),
|
|
RequirementID: requirement.RequirementID,
|
|
CapabilityKind: requirement.CapabilityKind,
|
|
CapabilityID: requirement.CapabilityID,
|
|
OwnerPrincipalID: requirement.OwnerPrincipalID,
|
|
OriginalIntentID: requirement.OriginalIntentID,
|
|
SourceUtteranceID: requirement.SourceUtteranceID,
|
|
Reason: reason,
|
|
Detail: detail,
|
|
Strategies: acquisitionStrategies(reason),
|
|
RecordedAtUnixMillis: millis,
|
|
}, nil
|
|
}
|
|
|
|
func exclusionReason(requirement capability.CapabilityResolutionRequirement, candidate capability.CapabilityImplementation) capability.GapReason {
|
|
if ValidateCapabilityImplementation(candidate) != nil {
|
|
return capability.GapIncompatible
|
|
}
|
|
if candidate.OwnerPrincipalID != requirement.OwnerPrincipalID {
|
|
return capability.GapUnauthorized
|
|
}
|
|
if candidate.Qualification != capability.QualificationValidated {
|
|
return capability.GapUnqualified
|
|
}
|
|
if candidate.Activation != capability.ActivationActive && !(requirement.AllowCanary && candidate.Activation == capability.ActivationCanary) {
|
|
return capability.GapInactive
|
|
}
|
|
if candidate.Health != capability.HealthAvailable {
|
|
return capability.GapUnhealthy
|
|
}
|
|
if requirement.AuthorityContextRef != "" && candidate.AuthorityPolicyRef != requirement.AuthorityContextRef {
|
|
return capability.GapUnauthorized
|
|
}
|
|
for _, required := range requirement.RequiredConstraints {
|
|
if !slices.Contains(candidate.DataConstraints, required) {
|
|
return capability.GapIncompatible
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func betterImplementation(left, right capability.CapabilityImplementation) bool {
|
|
if left.ReliabilityMillis != nil && right.ReliabilityMillis != nil && *left.ReliabilityMillis != *right.ReliabilityMillis {
|
|
return *left.ReliabilityMillis > *right.ReliabilityMillis
|
|
}
|
|
if left.LatencyScoreMillis != nil && right.LatencyScoreMillis != nil && *left.LatencyScoreMillis != *right.LatencyScoreMillis {
|
|
return *left.LatencyScoreMillis < *right.LatencyScoreMillis
|
|
}
|
|
if left.CostScoreMillis != nil && right.CostScoreMillis != nil && *left.CostScoreMillis != *right.CostScoreMillis {
|
|
return *left.CostScoreMillis < *right.CostScoreMillis
|
|
}
|
|
return string(left.ID) < string(right.ID)
|
|
}
|
|
|
|
func gapDetail(reason capability.GapReason) string {
|
|
switch reason {
|
|
case capability.GapNoImplementation:
|
|
return "no registered implementation satisfies this capability requirement"
|
|
case capability.GapUnhealthy:
|
|
return "registered implementations are currently unavailable or degraded"
|
|
case capability.GapInactive:
|
|
return "registered implementations are not active for this use"
|
|
case capability.GapUnqualified:
|
|
return "registered implementations are not validated"
|
|
case capability.GapUnauthorized:
|
|
return "registered implementations are not allowed by the current authority or policy context"
|
|
case capability.GapIncompatible:
|
|
return "registered implementations do not match the required modality, data or environment constraints"
|
|
default:
|
|
return "capability availability is unknown"
|
|
}
|
|
}
|
|
|
|
func acquisitionStrategies(reason capability.GapReason) []capability.AcquisitionStrategyCandidate {
|
|
switch reason {
|
|
case capability.GapNoImplementation:
|
|
return []capability.AcquisitionStrategyCandidate{
|
|
{Kind: capability.AcquireDiscoverExisting, Reason: "look for an already available adapter or service"},
|
|
{Kind: capability.AcquireConnectExisting, Reason: "connect an existing provider after explicit authority"},
|
|
{Kind: capability.AcquireBuildNew, Reason: "build a new implementation through a future approved coding capability"},
|
|
}
|
|
case capability.GapUnhealthy:
|
|
return []capability.AcquisitionStrategyCandidate{
|
|
{Kind: capability.AcquireUseAlternative, Reason: "try another eligible implementation"},
|
|
{Kind: capability.AcquireConfigureExisting, Reason: "repair or reconfigure the registered implementation"},
|
|
}
|
|
case capability.GapUnauthorized:
|
|
return []capability.AcquisitionStrategyCandidate{{Kind: capability.AcquireRequestUserInput, Reason: "request explicit authority or policy decision"}}
|
|
default:
|
|
return []capability.AcquisitionStrategyCandidate{{Kind: capability.AcquireUnresolvableCurrently, Reason: strings.ToLower(gapDetail(reason))}}
|
|
}
|
|
}
|