Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7ad996cae | ||
|
|
a065382512 | ||
|
|
8a8e571905 | ||
|
|
301dbdcfb2 |
@@ -2,6 +2,7 @@ package postgres
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.cin.su/m/Assistant/contracts/identity"
|
"git.cin.su/m/Assistant/contracts/identity"
|
||||||
@@ -18,6 +19,38 @@ func OpenSituation(ctx context.Context, config ContinuityConfig) (*SituationRepo
|
|||||||
}
|
}
|
||||||
return &SituationRepository{pool: pool}, nil
|
return &SituationRepository{pool: pool}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *SituationRepository) SaveContextScope(ctx context.Context, scope situation.ContextScope) error {
|
||||||
|
if err := scope.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
bindings, err := json.Marshal(scope.Bindings)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = r.pool.Exec(ctx, `INSERT INTO situation.context_scopes(context_scope_id,principal_id,revision,bindings,recorded_at_unix_millis) VALUES($1,$2,$3,$4,$5) ON CONFLICT(context_scope_id) DO UPDATE SET revision=EXCLUDED.revision, bindings=EXCLUDED.bindings, recorded_at_unix_millis=EXCLUDED.recorded_at_unix_millis`, scope.ID, scope.PrincipalID, scope.Revision, bindings, scope.RecordedAt)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SituationRepository) SaveFocusFrame(ctx context.Context, frame situation.FocusFrame) error {
|
||||||
|
if err := frame.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ref, err := json.Marshal(frame.ContextScopeRef)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = r.pool.Exec(ctx, `INSERT INTO situation.focus_frames(focus_frame_id,principal_id,actor_id,revision,authority_scope_ref,context_scope_ref,goal_ref,task_ref,source_utterance_ref,provenance,confidence_millis,state,parent_frame_ref,activated_at_unix_millis,suspended_at_unix_millis,expired_at_unix_millis,recorded_at_unix_millis) VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17) ON CONFLICT(focus_frame_id) DO UPDATE SET revision=EXCLUDED.revision, context_scope_ref=EXCLUDED.context_scope_ref, state=EXCLUDED.state, suspended_at_unix_millis=EXCLUDED.suspended_at_unix_millis, expired_at_unix_millis=EXCLUDED.expired_at_unix_millis, recorded_at_unix_millis=EXCLUDED.recorded_at_unix_millis`, frame.ID, frame.PrincipalID, frame.ActorID, frame.Revision, frame.AuthorityScopeRef, ref, frame.GoalRef, frame.TaskRef, frame.SourceUtteranceRef, frame.Provenance, frame.ConfidenceMillis, frame.State, frame.ParentFrameRef, frame.ActivatedAt, frame.SuspendedAt, frame.ExpiredAt, frame.RecordedAt)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SituationRepository) AppendFocusTransition(ctx context.Context, transition situation.FocusTransition) error {
|
||||||
|
if err := transition.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err := r.pool.Exec(ctx, `INSERT INTO situation.focus_transitions(transition_id,focus_frame_id,from_revision,to_revision,operation,cause_ref,recorded_at_unix_millis) VALUES($1,$2,$3,$4,$5,$6,$7)`, transition.TransitionID, transition.FrameID, transition.FromRevision, transition.ToRevision, transition.Operation, transition.CauseRef, transition.RecordedAt)
|
||||||
|
return err
|
||||||
|
}
|
||||||
func (r *SituationRepository) Close() { r.pool.Close() }
|
func (r *SituationRepository) Close() { r.pool.Close() }
|
||||||
func (r *SituationRepository) Save(ctx context.Context, binding situation.FocusBinding) error {
|
func (r *SituationRepository) Save(ctx context.Context, binding situation.FocusBinding) error {
|
||||||
_, err := r.pool.Exec(ctx, `INSERT INTO situation.focus_bindings (focus_binding_id, actor_id, session_id, workspace_id, project_id, goal_id, scope_id, active) VALUES ($1,$2,$3,$4,$5,$6,$7,$8) ON CONFLICT (focus_binding_id) DO UPDATE SET active=EXCLUDED.active`, binding.ID, binding.ActorID, binding.SessionID, binding.Workspace, binding.ProjectID, binding.GoalID, binding.ScopeID, binding.Active)
|
_, err := r.pool.Exec(ctx, `INSERT INTO situation.focus_bindings (focus_binding_id, actor_id, session_id, workspace_id, project_id, goal_id, scope_id, active) VALUES ($1,$2,$3,$4,$5,$6,$7,$8) ON CONFLICT (focus_binding_id) DO UPDATE SET active=EXCLUDED.active`, binding.ID, binding.ActorID, binding.SessionID, binding.Workspace, binding.ProjectID, binding.GoalID, binding.ScopeID, binding.Active)
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
package epistemics
|
package epistemics
|
||||||
|
|
||||||
|
import "git.cin.su/m/Assistant/platform/ids"
|
||||||
|
|
||||||
type BeliefState string
|
type BeliefState string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -9,19 +11,27 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Observation struct {
|
type Observation struct {
|
||||||
ID ObservationID
|
ID ObservationID
|
||||||
SourceSnapshot SourceSnapshotID
|
SourceSnapshot SourceSnapshotID
|
||||||
Excerpt SourceExcerptID
|
Excerpt SourceExcerptID
|
||||||
Text string
|
Text string
|
||||||
ObservedAt int64
|
ObservedAt int64
|
||||||
|
PrincipalID string
|
||||||
|
ContextScopeRef string
|
||||||
|
ObservationKind string
|
||||||
|
Payload string
|
||||||
|
ExtractorRef string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Claim struct {
|
type Claim struct {
|
||||||
ID ClaimID
|
ID ClaimID
|
||||||
Statement string
|
Statement string
|
||||||
ObservationID ObservationID
|
ObservationID ObservationID
|
||||||
CitationID CitationID
|
CitationID CitationID
|
||||||
Verification VerificationState
|
Verification VerificationState
|
||||||
|
PropositionID ids.ID
|
||||||
|
RevisedFrom *ClaimID
|
||||||
|
RecordedAtUnixMillis int64
|
||||||
}
|
}
|
||||||
|
|
||||||
type Belief struct {
|
type Belief struct {
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ type HelpMethodKind string
|
|||||||
type OutcomeClass string
|
type OutcomeClass string
|
||||||
type ReflectionTriggerKind string
|
type ReflectionTriggerKind string
|
||||||
type ReflectionCandidateState string
|
type ReflectionCandidateState string
|
||||||
|
type PropositionKind string
|
||||||
|
type BeliefAssessmentState string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
VerificationUnverified VerificationState = "UNVERIFIED"
|
VerificationUnverified VerificationState = "UNVERIFIED"
|
||||||
@@ -73,6 +75,12 @@ const (
|
|||||||
ReflectionCandidateEvaluating ReflectionCandidateState = "EVALUATING"
|
ReflectionCandidateEvaluating ReflectionCandidateState = "EVALUATING"
|
||||||
ReflectionCandidateSuperseded ReflectionCandidateState = "SUPERSEDED"
|
ReflectionCandidateSuperseded ReflectionCandidateState = "SUPERSEDED"
|
||||||
ReflectionCandidateResolved ReflectionCandidateState = "RESOLVED"
|
ReflectionCandidateResolved ReflectionCandidateState = "RESOLVED"
|
||||||
|
PropositionWorldFacet PropositionKind = "WORLD_FACET"
|
||||||
|
PropositionWorldRelation PropositionKind = "WORLD_RELATION"
|
||||||
|
PropositionOwnerDefined PropositionKind = "OWNER_DEFINED"
|
||||||
|
PropositionTextual PropositionKind = "TEXTUAL"
|
||||||
|
BeliefAssessmentCurrent BeliefAssessmentState = "CURRENT"
|
||||||
|
BeliefAssessmentSuperseded BeliefAssessmentState = "SUPERSEDED"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SystemExpectation records what Domovoy expected a meaningful help path to
|
// SystemExpectation records what Domovoy expected a meaningful help path to
|
||||||
@@ -134,3 +142,24 @@ type ReflectionCandidate struct {
|
|||||||
ClaimedBy string
|
ClaimedBy string
|
||||||
ClaimExpiresAtUnixMillis int64
|
ClaimExpiresAtUnixMillis int64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Proposition struct {
|
||||||
|
ID ids.ID
|
||||||
|
Kind PropositionKind
|
||||||
|
SubjectRef string
|
||||||
|
PredicateRef string
|
||||||
|
ObjectValue string
|
||||||
|
ObjectRef string
|
||||||
|
Qualifiers []string
|
||||||
|
TextFallback string
|
||||||
|
}
|
||||||
|
|
||||||
|
type BeliefAssessment struct {
|
||||||
|
ID ids.ID
|
||||||
|
ClaimID ClaimID
|
||||||
|
Revision ids.Revision
|
||||||
|
State BeliefAssessmentState
|
||||||
|
ConfidenceMillis int64
|
||||||
|
EvidenceRefs []EvidenceRef
|
||||||
|
AssessedAtUnixMillis int64
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
package situation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.cin.su/m/Assistant/contracts/authority"
|
||||||
|
"git.cin.su/m/Assistant/contracts/identity"
|
||||||
|
"git.cin.su/m/Assistant/contracts/work"
|
||||||
|
"git.cin.su/m/Assistant/platform/clock"
|
||||||
|
"git.cin.su/m/Assistant/platform/ids"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ContextScopeID ids.StableID
|
||||||
|
type FocusFrameID ids.StableID
|
||||||
|
type FocusTransitionID ids.StableID
|
||||||
|
type FocusRevision ids.Revision
|
||||||
|
|
||||||
|
type RoleKey string
|
||||||
|
type MatchPolicy string
|
||||||
|
type FocusFrameState string
|
||||||
|
type FocusProvenance string
|
||||||
|
type FocusTransitionOperation string
|
||||||
|
|
||||||
|
const (
|
||||||
|
MatchRequired MatchPolicy = "REQUIRED"
|
||||||
|
MatchPreferred MatchPolicy = "PREFERRED"
|
||||||
|
MatchRelated MatchPolicy = "RELATED"
|
||||||
|
MatchExcluded MatchPolicy = "EXCLUDED"
|
||||||
|
|
||||||
|
RolePerson RoleKey = "PERSON"
|
||||||
|
RoleOrganization RoleKey = "ORGANIZATION"
|
||||||
|
RoleFamily RoleKey = "FAMILY"
|
||||||
|
RoleEnvironment RoleKey = "ENVIRONMENT"
|
||||||
|
RoleLocation RoleKey = "LOCATION"
|
||||||
|
RoleSystem RoleKey = "SYSTEM"
|
||||||
|
RoleDatabase RoleKey = "DATABASE"
|
||||||
|
RoleRepository RoleKey = "REPOSITORY"
|
||||||
|
RoleBranch RoleKey = "BRANCH"
|
||||||
|
RoleVersion RoleKey = "VERSION"
|
||||||
|
RoleActivity RoleKey = "ACTIVITY"
|
||||||
|
RoleProject RoleKey = "PROJECT"
|
||||||
|
RoleActiveEntity RoleKey = "ACTIVE_ENTITY"
|
||||||
|
|
||||||
|
FocusCandidate FocusFrameState = "CANDIDATE"
|
||||||
|
FocusActive FocusFrameState = "ACTIVE"
|
||||||
|
FocusSuspended FocusFrameState = "SUSPENDED"
|
||||||
|
FocusExpired FocusFrameState = "EXPIRED"
|
||||||
|
FocusSuperseded FocusFrameState = "SUPERSEDED"
|
||||||
|
|
||||||
|
FocusExplicitHuman FocusProvenance = "EXPLICIT_HUMAN"
|
||||||
|
FocusUserConfirmed FocusProvenance = "USER_CONFIRMED"
|
||||||
|
FocusInherited FocusProvenance = "INHERITED"
|
||||||
|
FocusEntityReference FocusProvenance = "ENTITY_REFERENCE"
|
||||||
|
FocusTaskReference FocusProvenance = "TASK_REFERENCE"
|
||||||
|
FocusRepositoryReference FocusProvenance = "REPOSITORY_REFERENCE"
|
||||||
|
FocusEnvironmentObservation FocusProvenance = "ENVIRONMENT_OBSERVATION"
|
||||||
|
FocusInferred FocusProvenance = "INFERRED"
|
||||||
|
|
||||||
|
TransitionActivate FocusTransitionOperation = "ACTIVATE"
|
||||||
|
TransitionSuspend FocusTransitionOperation = "SUSPEND"
|
||||||
|
TransitionResume FocusTransitionOperation = "RESUME"
|
||||||
|
TransitionSupersede FocusTransitionOperation = "SUPERSEDE"
|
||||||
|
TransitionExpire FocusTransitionOperation = "EXPIRE"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ContextBinding struct {
|
||||||
|
RoleKey RoleKey
|
||||||
|
TargetRef SemanticReference
|
||||||
|
MatchPolicy MatchPolicy
|
||||||
|
Provenance string
|
||||||
|
ConfidenceMillis int64
|
||||||
|
SourceRef *SemanticReference
|
||||||
|
}
|
||||||
|
|
||||||
|
type ContextScope struct {
|
||||||
|
ID ContextScopeID
|
||||||
|
PrincipalID identity.PrincipalID
|
||||||
|
Revision FocusRevision
|
||||||
|
Bindings []ContextBinding
|
||||||
|
RecordedAt clock.Instant
|
||||||
|
}
|
||||||
|
|
||||||
|
type FocusFrame struct {
|
||||||
|
ID FocusFrameID
|
||||||
|
PrincipalID identity.PrincipalID
|
||||||
|
ActorID identity.ActorID
|
||||||
|
Revision FocusRevision
|
||||||
|
AuthorityScopeRef authority.ScopeID
|
||||||
|
ContextScopeRef SemanticReference
|
||||||
|
GoalRef *work.GoalID
|
||||||
|
TaskRef *work.TaskID
|
||||||
|
SourceUtteranceRef string
|
||||||
|
Provenance FocusProvenance
|
||||||
|
ConfidenceMillis int64
|
||||||
|
State FocusFrameState
|
||||||
|
ParentFrameRef *FocusFrameID
|
||||||
|
ActivatedAt clock.Instant
|
||||||
|
SuspendedAt *clock.Instant
|
||||||
|
ExpiredAt *clock.Instant
|
||||||
|
RecordedAt clock.Instant
|
||||||
|
}
|
||||||
|
|
||||||
|
type FocusTransition struct {
|
||||||
|
TransitionID FocusTransitionID
|
||||||
|
FrameID FocusFrameID
|
||||||
|
FromRevision FocusRevision
|
||||||
|
ToRevision FocusRevision
|
||||||
|
Operation FocusTransitionOperation
|
||||||
|
CauseRef string
|
||||||
|
RecordedAt clock.Instant
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s ContextScope) Validate() error {
|
||||||
|
if s.ID == "" || s.PrincipalID == "" || !ids.Revision(s.Revision).Valid() || !s.RecordedAt.Valid() {
|
||||||
|
return fmt.Errorf("invalid context scope")
|
||||||
|
}
|
||||||
|
for _, b := range s.Bindings {
|
||||||
|
if b.RoleKey == "" || !b.TargetRef.Valid() || !validMatchPolicy(b.MatchPolicy) {
|
||||||
|
return fmt.Errorf("invalid context binding")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FocusFrame) Validate() error {
|
||||||
|
if f.ID == "" || f.PrincipalID == "" || f.ActorID == "" || !ids.Revision(f.Revision).Valid() || !f.ContextScopeRef.Valid() || f.State == "" || f.Provenance == "" || !f.RecordedAt.Valid() {
|
||||||
|
return fmt.Errorf("invalid focus frame")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t FocusTransition) Validate() error {
|
||||||
|
if t.TransitionID == "" || t.FrameID == "" || !ids.Revision(t.FromRevision).Valid() || !ids.Revision(t.ToRevision).Valid() || t.Operation == "" || !t.RecordedAt.Valid() {
|
||||||
|
return fmt.Errorf("invalid focus transition")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validMatchPolicy(p MatchPolicy) bool {
|
||||||
|
return p == MatchRequired || p == MatchPreferred || p == MatchRelated || p == MatchExcluded
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package situation_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.cin.su/m/Assistant/contracts/identity"
|
||||||
|
contract "git.cin.su/m/Assistant/contracts/situation"
|
||||||
|
"git.cin.su/m/Assistant/platform/clock"
|
||||||
|
"git.cin.su/m/Assistant/platform/ids"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFocusV2ContractsValidateTypedScopeAndFrame(t *testing.T) {
|
||||||
|
rev := ids.Revision(1)
|
||||||
|
scope := contract.ContextScope{ID: "scope", PrincipalID: identity.PrincipalID("company-a"), Revision: contract.FocusRevision(rev), RecordedAt: clock.Instant(1), Bindings: []contract.ContextBinding{{RoleKey: contract.RoleOrganization, TargetRef: contract.SemanticReference{OwnerNamespace: "world", SemanticKind: "organization", StableID: "a"}, MatchPolicy: contract.MatchRequired}}}
|
||||||
|
if err := scope.Validate(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
frame := contract.FocusFrame{ID: "frame", PrincipalID: scope.PrincipalID, ActorID: "actor", Revision: 1, ContextScopeRef: contract.SemanticReference{OwnerNamespace: "situation", SemanticKind: "context_scope", StableID: ids.StableID(scope.ID), Revision: &rev}, State: contract.FocusActive, Provenance: contract.FocusExplicitHuman, RecordedAt: 1}
|
||||||
|
if err := frame.Validate(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -12,6 +12,48 @@ import (
|
|||||||
// a reference only; it does not expose another owner's storage.
|
// a reference only; it does not expose another owner's storage.
|
||||||
type SemanticReference = ids.SemanticRef
|
type SemanticReference = ids.SemanticRef
|
||||||
|
|
||||||
|
// VersionBinding is a typed applicability constraint. Absence of VersionRef
|
||||||
|
// means unknown, never an implicit "latest".
|
||||||
|
type VersionMatchMode string
|
||||||
|
|
||||||
|
const (
|
||||||
|
VersionExact VersionMatchMode = "EXACT"
|
||||||
|
VersionCompatibleFamily VersionMatchMode = "COMPATIBLE_FAMILY"
|
||||||
|
VersionAtOrBefore VersionMatchMode = "AT_OR_BEFORE"
|
||||||
|
VersionUnknown VersionMatchMode = "UNKNOWN"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VersionBinding struct {
|
||||||
|
SubjectRef SemanticReference
|
||||||
|
VersionRef SemanticReference
|
||||||
|
MatchMode VersionMatchMode
|
||||||
|
}
|
||||||
|
|
||||||
|
type EnvironmentReference struct {
|
||||||
|
EnvironmentRef SemanticReference
|
||||||
|
VersionRef *SemanticReference
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b VersionBinding) Validate() error {
|
||||||
|
if !b.SubjectRef.Valid() || !b.VersionRef.Valid() {
|
||||||
|
return fmt.Errorf("version binding references are invalid")
|
||||||
|
}
|
||||||
|
if b.MatchMode != VersionExact && b.MatchMode != VersionCompatibleFamily && b.MatchMode != VersionAtOrBefore && b.MatchMode != VersionUnknown {
|
||||||
|
return fmt.Errorf("version binding match mode is invalid")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r EnvironmentReference) Validate() error {
|
||||||
|
if !r.EnvironmentRef.Valid() {
|
||||||
|
return fmt.Errorf("environment reference is invalid")
|
||||||
|
}
|
||||||
|
if r.VersionRef != nil && !r.VersionRef.Valid() {
|
||||||
|
return fmt.Errorf("environment version reference is invalid")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type PrincipalRealm string
|
type PrincipalRealm string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
BEGIN;
|
||||||
|
|
||||||
|
SET ROLE situation_role;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS situation.context_scopes (
|
||||||
|
context_scope_id TEXT PRIMARY KEY,
|
||||||
|
principal_id TEXT NOT NULL,
|
||||||
|
revision BIGINT NOT NULL CHECK (revision > 0),
|
||||||
|
bindings JSONB NOT NULL,
|
||||||
|
recorded_at_unix_millis BIGINT NOT NULL CHECK (recorded_at_unix_millis > 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS situation.focus_frames (
|
||||||
|
focus_frame_id TEXT PRIMARY KEY,
|
||||||
|
principal_id TEXT NOT NULL,
|
||||||
|
actor_id TEXT NOT NULL,
|
||||||
|
revision BIGINT NOT NULL CHECK (revision > 0),
|
||||||
|
authority_scope_ref TEXT NOT NULL,
|
||||||
|
context_scope_ref JSONB NOT NULL,
|
||||||
|
goal_ref TEXT,
|
||||||
|
task_ref TEXT,
|
||||||
|
source_utterance_ref TEXT,
|
||||||
|
provenance TEXT NOT NULL,
|
||||||
|
confidence_millis BIGINT NOT NULL CHECK (confidence_millis >= 0),
|
||||||
|
state TEXT NOT NULL,
|
||||||
|
parent_frame_ref TEXT,
|
||||||
|
activated_at_unix_millis BIGINT NOT NULL CHECK (activated_at_unix_millis > 0),
|
||||||
|
suspended_at_unix_millis BIGINT,
|
||||||
|
expired_at_unix_millis BIGINT,
|
||||||
|
recorded_at_unix_millis BIGINT NOT NULL CHECK (recorded_at_unix_millis > 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS situation.focus_transitions (
|
||||||
|
transition_id TEXT PRIMARY KEY,
|
||||||
|
focus_frame_id TEXT NOT NULL,
|
||||||
|
from_revision BIGINT NOT NULL CHECK (from_revision > 0),
|
||||||
|
to_revision BIGINT NOT NULL CHECK (to_revision > 0),
|
||||||
|
operation TEXT NOT NULL,
|
||||||
|
cause_ref TEXT NOT NULL,
|
||||||
|
recorded_at_unix_millis BIGINT NOT NULL CHECK (recorded_at_unix_millis > 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS focus_transitions_frame_order ON situation.focus_transitions(focus_frame_id, recorded_at_unix_millis, transition_id);
|
||||||
|
GRANT SELECT, INSERT, UPDATE ON situation.context_scopes, situation.focus_frames, situation.focus_transitions TO situation_role;
|
||||||
|
|
||||||
|
RESET ROLE;
|
||||||
|
COMMIT;
|
||||||
@@ -1,5 +1 @@
|
|||||||
{
|
{"task_file":"dev/tasks/GEN2-007-general-freshness-volatility-temporal-applicability-and-revalidation.json","task_id":"GEN2-007","task_revision":1}
|
||||||
"task_file": "dev/tasks/GEN2-003-add-narrow-world-owner-and-typed-vocabulary.json",
|
|
||||||
"task_revision": 1,
|
|
||||||
"task_id": "GEN2-003"
|
|
||||||
}
|
|
||||||
@@ -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}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package domain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"git.cin.su/m/Assistant/contracts/epistemics"
|
||||||
|
"git.cin.su/m/Assistant/platform/ids"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrInvalidKnowledge = errors.New("invalid epistemic knowledge record")
|
||||||
|
|
||||||
|
func ValidateProposition(value epistemics.Proposition) error {
|
||||||
|
if value.ID == "" || value.Kind == "" || strings.TrimSpace(value.SubjectRef) == "" || (value.ObjectValue == "" && value.ObjectRef == "" && value.TextFallback == "") {
|
||||||
|
return ErrInvalidKnowledge
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ValidateBeliefAssessment(value epistemics.BeliefAssessment) error {
|
||||||
|
if value.ID == "" || value.ClaimID == "" || !value.Revision.Valid() || value.State == "" || value.ConfidenceMillis < 0 || value.ConfidenceMillis > 1000 || value.AssessedAtUnixMillis < 1 {
|
||||||
|
return ErrInvalidKnowledge
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBeliefAssessment(claim epistemics.ClaimID, confidence int64, state epistemics.BeliefAssessmentState, evidence []epistemics.EvidenceRef, at int64) (epistemics.BeliefAssessment, error) {
|
||||||
|
id, err := ids.New()
|
||||||
|
if err != nil {
|
||||||
|
return epistemics.BeliefAssessment{}, err
|
||||||
|
}
|
||||||
|
value := epistemics.BeliefAssessment{ID: id, ClaimID: claim, Revision: ids.InitialRevision, State: state, ConfidenceMillis: confidence, EvidenceRefs: append([]epistemics.EvidenceRef(nil), evidence...), AssessedAtUnixMillis: at}
|
||||||
|
if err := ValidateBeliefAssessment(value); err != nil {
|
||||||
|
return epistemics.BeliefAssessment{}, err
|
||||||
|
}
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package domain_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.cin.su/m/Assistant/contracts/epistemics"
|
||||||
|
"git.cin.su/m/Assistant/modules/epistemics/domain"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestKnowledgeV2SeparatesPropositionAndReassessableBelief(t *testing.T) {
|
||||||
|
p := epistemics.Proposition{ID: "p", Kind: epistemics.PropositionWorldFacet, SubjectRef: "world:database:a", ObjectValue: "1.6.28"}
|
||||||
|
if err := domain.ValidateProposition(p); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
a, err := domain.NewBeliefAssessment("claim:a", 800, epistemics.BeliefAssessmentCurrent, []epistemics.EvidenceRef{"observation:a"}, 1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
a.Revision++
|
||||||
|
if err := domain.ValidateBeliefAssessment(a); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
package domain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
contract "git.cin.su/m/Assistant/contracts/situation"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FocusStack is a rebuildable, situation-owned projection. Push suspends the
|
||||||
|
// previous active frame; Pop resumes its parent and never deletes a frame.
|
||||||
|
type FocusStack struct{ frames []contract.FocusFrame }
|
||||||
|
|
||||||
|
func NewFocusStack(frames []contract.FocusFrame) (FocusStack, error) {
|
||||||
|
stack := FocusStack{frames: append([]contract.FocusFrame(nil), frames...)}
|
||||||
|
for _, frame := range stack.frames {
|
||||||
|
if err := frame.Validate(); err != nil {
|
||||||
|
return FocusStack{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return stack, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s FocusStack) Frames() []contract.FocusFrame {
|
||||||
|
return append([]contract.FocusFrame(nil), s.frames...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s FocusStack) Current() (contract.FocusFrame, error) {
|
||||||
|
for i := len(s.frames) - 1; i >= 0; i-- {
|
||||||
|
if s.frames[i].State == contract.FocusActive {
|
||||||
|
return s.frames[i], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return contract.FocusFrame{}, ErrNoActiveFocus
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s FocusStack) Push(frame contract.FocusFrame) (FocusStack, error) {
|
||||||
|
if err := frame.Validate(); err != nil {
|
||||||
|
return FocusStack{}, err
|
||||||
|
}
|
||||||
|
if frame.State != contract.FocusActive {
|
||||||
|
return FocusStack{}, fmt.Errorf("pushed frame must be active")
|
||||||
|
}
|
||||||
|
frames := s.Frames()
|
||||||
|
if len(frames) > 0 {
|
||||||
|
current, err := s.Current()
|
||||||
|
if err == nil {
|
||||||
|
for i := range frames {
|
||||||
|
if frames[i].ID == current.ID {
|
||||||
|
frames[i].State = contract.FocusSuspended
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
frame.ParentFrameRef = ¤t.ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
frames = append(frames, frame)
|
||||||
|
return FocusStack{frames: frames}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s FocusStack) Pop() (FocusStack, contract.FocusFrame, error) {
|
||||||
|
current, err := s.Current()
|
||||||
|
if err != nil {
|
||||||
|
return FocusStack{}, contract.FocusFrame{}, err
|
||||||
|
}
|
||||||
|
frames := s.Frames()
|
||||||
|
for i := range frames {
|
||||||
|
if frames[i].ID == current.ID {
|
||||||
|
frames[i].State = contract.FocusSuspended
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if current.ParentFrameRef != nil {
|
||||||
|
for i := range frames {
|
||||||
|
if frames[i].ID == *current.ParentFrameRef {
|
||||||
|
frames[i].State = contract.FocusActive
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return FocusStack{frames: frames}, current, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProjectFocusProjection retains the V1 API as a read projection only.
|
||||||
|
func ProjectFocusProjection(frame contract.FocusFrame, project contract.ProjectDescriptor) contract.ActiveProjectFocus {
|
||||||
|
return contract.ActiveProjectFocus{ActorID: frame.ActorID, PrincipalID: frame.PrincipalID, ProjectID: contract.ProjectID(project.ID), ProjectDisplayName: project.DisplayName, Provenance: contract.ProjectResolutionProvenance(frame.Provenance), ConfidenceMillis: frame.ConfidenceMillis, SourceUtteranceID: frame.SourceUtteranceRef, UpdatedAtUnixMillis: int64(frame.RecordedAt)}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package domain_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
contract "git.cin.su/m/Assistant/contracts/situation"
|
||||||
|
"git.cin.su/m/Assistant/modules/situation/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFocusStackPushPopKeepsParentAndSeparatesCompanies(t *testing.T) {
|
||||||
|
base := contract.FocusFrame{ID: "company-a", PrincipalID: "principal-a", ActorID: "actor", Revision: 1, ContextScopeRef: contract.SemanticReference{OwnerNamespace: "situation", SemanticKind: "context_scope", StableID: "scope-a"}, State: contract.FocusActive, Provenance: contract.FocusExplicitHuman, RecordedAt: 1}
|
||||||
|
stack, err := domain.NewFocusStack([]contract.FocusFrame{base})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
child := base
|
||||||
|
child.ID = "company-b"
|
||||||
|
child.PrincipalID = "principal-b"
|
||||||
|
child.ContextScopeRef.StableID = "scope-b"
|
||||||
|
stack, err = stack.Push(child)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
current, _ := stack.Current()
|
||||||
|
if current.ID != "company-b" || current.ParentFrameRef == nil || *current.ParentFrameRef != "company-a" {
|
||||||
|
t.Fatalf("unexpected current %#v", current)
|
||||||
|
}
|
||||||
|
stack, _, err = stack.Pop()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
current, _ = stack.Current()
|
||||||
|
if current.ID != "company-a" {
|
||||||
|
t.Fatalf("parent was not resumed: %#v", current)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package domain_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.cin.su/m/Assistant/contracts/world"
|
||||||
|
"git.cin.su/m/Assistant/modules/world/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestResolveCandidatesDoesNotAutoMergeAmbiguousPeopleOrSystems(t *testing.T) {
|
||||||
|
decision, candidates := domain.ResolveCandidates([]world.IdentityResolutionCandidate{
|
||||||
|
{ObjectRef: world.SemanticReference{OwnerNamespace: "world", SemanticKind: "person", StableID: "ivan-telegram"}, Confidence: 800},
|
||||||
|
{ObjectRef: world.SemanticReference{OwnerNamespace: "world", SemanticKind: "person", StableID: "ivan-crm"}, Confidence: 800},
|
||||||
|
}, 700)
|
||||||
|
if decision != world.ResolutionUnresolved || len(candidates) != 2 {
|
||||||
|
t.Fatalf("ambiguous result = %s, %#v", decision, candidates)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVersionAndEnvironmentReferencesAreTyped(t *testing.T) {
|
||||||
|
version := world.SemanticReference{OwnerNamespace: "world", SemanticKind: "version", StableID: "unf-3"}
|
||||||
|
binding := world.VersionBinding{SubjectRef: world.SemanticReference{OwnerNamespace: "world", SemanticKind: "database", StableID: "company-a-unf"}, VersionRef: version, MatchMode: world.VersionExact}
|
||||||
|
if err := binding.Validate(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
environment := world.EnvironmentReference{EnvironmentRef: world.SemanticReference{OwnerNamespace: "world", SemanticKind: "environment", StableID: "company-a-prod"}, VersionRef: &version}
|
||||||
|
if err := environment.Validate(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,8 @@ package domain
|
|||||||
|
|
||||||
import "git.cin.su/m/Assistant/contracts/world"
|
import "git.cin.su/m/Assistant/contracts/world"
|
||||||
|
|
||||||
|
import "sort"
|
||||||
|
|
||||||
func ValidateObject(value world.WorldObject) error { return value.Validate() }
|
func ValidateObject(value world.WorldObject) error { return value.Validate() }
|
||||||
|
|
||||||
func ValidateKindDefinition(value world.WorldObjectKindDefinition) error { return value.Validate() }
|
func ValidateKindDefinition(value world.WorldObjectKindDefinition) error { return value.Validate() }
|
||||||
@@ -15,3 +17,18 @@ func ValidateRelationDefinition(value world.RelationDefinition) error { return v
|
|||||||
func ValidateResolution(value world.IdentityResolutionDecisionRecord) error { return value.Validate() }
|
func ValidateResolution(value world.IdentityResolutionDecisionRecord) error { return value.Validate() }
|
||||||
|
|
||||||
func ValidateLineage(value world.WorldLineage) error { return value.Validate() }
|
func ValidateLineage(value world.WorldLineage) error { return value.Validate() }
|
||||||
|
|
||||||
|
// ResolveCandidates deliberately refuses to merge an ambiguous result. A
|
||||||
|
// caller may persist the returned UNRESOLVED decision and later re-evaluate it
|
||||||
|
// with additional evidence.
|
||||||
|
func ResolveCandidates(candidates []world.IdentityResolutionCandidate, minimumConfidence int64) (world.ResolutionDecision, []world.IdentityResolutionCandidate) {
|
||||||
|
ordered := append([]world.IdentityResolutionCandidate(nil), candidates...)
|
||||||
|
sort.SliceStable(ordered, func(i, j int) bool { return ordered[i].Confidence > ordered[j].Confidence })
|
||||||
|
if len(ordered) == 0 || ordered[0].Confidence < minimumConfidence {
|
||||||
|
return world.ResolutionUnresolved, ordered
|
||||||
|
}
|
||||||
|
if len(ordered) > 1 && ordered[0].Confidence == ordered[1].Confidence {
|
||||||
|
return world.ResolutionUnresolved, ordered
|
||||||
|
}
|
||||||
|
return world.ResolutionSame, ordered[:1]
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user