Files
Assistant/modules/continuity/domain/prospective.go
m 5d4e064a94
CI / test (push) Successful in 16s
CI / test (pull_request) Successful in 15s
DEV-000065 assistant presence attention surface
2026-08-17 16:23:16 +03:00

163 lines
5.8 KiB
Go

package domain
import (
"errors"
"strings"
contract "git.cin.su/m/Assistant/contracts/continuity"
)
var (
ErrProspectiveNotFound = errors.New("prospective item not found")
ErrProspectiveRevision = errors.New("prospective item revision conflict")
ErrInvalidTransition = errors.New("invalid prospective item transition")
ErrInvalidSatisfaction = errors.New("invalid delivered satisfaction command")
ErrInvalidReminder = errors.New("invalid reminder request")
ErrInvalidAttention = errors.New("invalid attention item")
)
type Store struct {
items map[contract.ProspectiveItemID]contract.ProspectiveItem
}
func ValidateSatisfyDelivered(command contract.SatisfyDeliveredCommand) error {
if command.ActionIntentID == "" || command.AuthorizedActionID == "" || command.OwnerPrincipalID == "" ||
command.Subject.ProspectiveItemID == "" || command.Subject.ExpectedRevision == 0 ||
command.Subject.FromState != contract.ProspectiveDelivered || command.Subject.ToState != contract.ProspectiveSatisfied ||
strings.TrimSpace(command.Subject.EvidenceRef) == "" {
return ErrInvalidSatisfaction
}
return nil
}
func BuildReminder(request contract.ScheduleReminderRequest, id contract.ProspectiveItemID) (contract.ProspectiveItem, error) {
if id == "" || request.OwnerPrincipalID == "" || request.ScopeID == "" || strings.TrimSpace(request.SourceIntentID) == "" ||
strings.TrimSpace(request.SourceUtteranceID) == "" || strings.TrimSpace(request.Summary) == "" ||
request.TriggerAtUnixMillis <= request.RequestedAtUnixMillis || request.RequestedAtUnixMillis < 1 {
return contract.ProspectiveItem{}, ErrInvalidReminder
}
return contract.ProspectiveItem{
ID: id,
CommitmentID: contract.CommitmentID(""),
OwnerPrincipalID: request.OwnerPrincipalID,
ScopeID: request.ScopeID,
GoalID: "",
SourceIntentID: strings.TrimSpace(request.SourceIntentID),
SourceUtteranceID: strings.TrimSpace(request.SourceUtteranceID),
Summary: strings.TrimSpace(request.Summary),
TriggerAtUnixMillis: request.TriggerAtUnixMillis,
State: contract.ProspectiveArmed,
Revision: 1,
}, nil
}
func BuildDueReminderAttention(item contract.ProspectiveItem, id contract.AttentionItemID, nowUnixMillis int64) (contract.AttentionItem, error) {
if id == "" || item.ID == "" || item.OwnerPrincipalID == "" || item.ScopeID == "" ||
strings.TrimSpace(item.Summary) == "" || item.TriggerAtUnixMillis < 1 || nowUnixMillis < 1 {
return contract.AttentionItem{}, ErrInvalidAttention
}
if item.State != contract.ProspectiveDelivered && item.State != contract.ProspectiveRevalidating {
return contract.AttentionItem{}, ErrInvalidAttention
}
return contract.AttentionItem{
ID: id,
OwnerPrincipalID: item.OwnerPrincipalID,
ScopeID: item.ScopeID,
SourceKind: contract.AttentionSourceProspectiveReminder,
SourceProspectiveID: item.ID,
Level: contract.AttentionDue,
State: contract.AttentionPending,
Summary: item.Summary,
DueAtUnixMillis: item.TriggerAtUnixMillis,
CreatedAtUnixMillis: nowUnixMillis,
Revision: 1,
}, nil
}
func (s *Store) SatisfyDelivered(id contract.ProspectiveItemID, revision contract.ProspectiveRevision, evidenceRef string) (contract.ProspectiveItem, error) {
item, err := s.current(id, revision)
if err != nil {
return contract.ProspectiveItem{}, err
}
if item.State != contract.ProspectiveDelivered || strings.TrimSpace(evidenceRef) == "" {
return contract.ProspectiveItem{}, ErrInvalidTransition
}
item.State = contract.ProspectiveSatisfied
item.Revision++
s.items[id] = item
return item, nil
}
func Recover(items []contract.ProspectiveItem) Store {
stored := make(map[contract.ProspectiveItemID]contract.ProspectiveItem, len(items))
for _, item := range items {
stored[item.ID] = item
}
return Store{items: stored}
}
func (s *Store) Snapshot() []contract.ProspectiveItem {
items := make([]contract.ProspectiveItem, 0, len(s.items))
for _, item := range s.items {
items = append(items, item)
}
return items
}
func (s *Store) Trigger(id contract.ProspectiveItemID, revision contract.ProspectiveRevision) (contract.ProspectiveItem, error) {
item, err := s.current(id, revision)
if err != nil {
return contract.ProspectiveItem{}, err
}
if item.State != contract.ProspectiveArmed {
return contract.ProspectiveItem{}, ErrInvalidTransition
}
item.State = contract.ProspectiveTriggered
item.Revision++
s.items[id] = item
return item, nil
}
func (s *Store) BeginRevalidation(id contract.ProspectiveItemID, revision contract.ProspectiveRevision) (contract.ProspectiveItem, error) {
item, err := s.current(id, revision)
if err != nil {
return contract.ProspectiveItem{}, err
}
if item.State != contract.ProspectiveTriggered {
return contract.ProspectiveItem{}, ErrInvalidTransition
}
item.State = contract.ProspectiveRevalidating
item.Revision++
s.items[id] = item
return item, nil
}
func (s *Store) FinishRevalidation(id contract.ProspectiveItemID, revision contract.ProspectiveRevision, relevant bool) (contract.ProspectiveItem, error) {
item, err := s.current(id, revision)
if err != nil {
return contract.ProspectiveItem{}, err
}
if item.State != contract.ProspectiveRevalidating {
return contract.ProspectiveItem{}, ErrInvalidTransition
}
if relevant {
item.State = contract.ProspectiveDelivered
} else {
item.State = contract.ProspectiveSuppressed
}
item.Revision++
s.items[id] = item
return item, nil
}
func (s *Store) current(id contract.ProspectiveItemID, revision contract.ProspectiveRevision) (contract.ProspectiveItem, error) {
item, ok := s.items[id]
if !ok {
return contract.ProspectiveItem{}, ErrProspectiveNotFound
}
if item.Revision != revision {
return contract.ProspectiveItem{}, ErrProspectiveRevision
}
return item, nil
}