Files
Assistant/modules/situation/domain/focus.go
2026-08-16 15:22:49 +03:00

61 lines
1.7 KiB
Go

package domain
import (
"errors"
contract "git.cin.su/m/Assistant/contracts/situation"
"git.cin.su/m/Assistant/contracts/work"
)
var (
ErrNoActiveFocus = errors.New("no active focus binding")
ErrStaleFocus = errors.New("stale focus binding")
ErrAmbiguousFocus = errors.New("ambiguous focus binding")
ErrTargetMismatch = errors.New("proposed target does not match resolved focus")
)
// Resolver is a deterministic situation-owned resolver. It accepts runtime
// bindings only; RequestText must never be used to select a project.
type Resolver struct {
bindings []contract.FocusBinding
}
func NewResolver(bindings []contract.FocusBinding) Resolver {
return Resolver{bindings: append([]contract.FocusBinding(nil), bindings...)}
}
func (r Resolver) Resolve(runtime contract.RuntimeBinding) (contract.FocusBinding, error) {
var matches []contract.FocusBinding
var stale bool
for _, binding := range r.bindings {
if binding.SessionID != runtime.SessionID || binding.Workspace != runtime.Workspace {
continue
}
if !binding.Active {
stale = true
continue
}
matches = append(matches, binding)
}
switch len(matches) {
case 0:
if stale {
return contract.FocusBinding{}, ErrStaleFocus
}
return contract.FocusBinding{}, ErrNoActiveFocus
case 1:
return matches[0], nil
default:
return contract.FocusBinding{}, ErrAmbiguousFocus
}
}
// ValidateTaskTarget prevents cognition from redirecting a request outside the
// resolved situation before a work owner considers its own revision guard.
func ValidateTaskTarget(focus contract.FocusBinding, target work.TaskTarget) error {
if focus.GoalID != target.GoalID || focus.ScopeID != target.ScopeID {
return ErrTargetMismatch
}
return nil
}