Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
301dbdcfb2 | ||
|
|
8037e66998 | ||
|
|
29815c49b8 |
@@ -2,6 +2,7 @@ package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"git.cin.su/m/Assistant/contracts/identity"
|
||||
@@ -18,6 +19,38 @@ func OpenSituation(ctx context.Context, config ContinuityConfig) (*SituationRepo
|
||||
}
|
||||
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) 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)
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"git.cin.su/m/Assistant/contracts/world"
|
||||
worlddomain "git.cin.su/m/Assistant/modules/world/domain"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
type WorldRepository struct{ pool *pgxpool.Pool }
|
||||
|
||||
func OpenWorld(ctx context.Context, config ContinuityConfig) (*WorldRepository, error) {
|
||||
pool, err := openPool(ctx, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &WorldRepository{pool: pool}, nil
|
||||
}
|
||||
|
||||
func (r *WorldRepository) Close() { r.pool.Close() }
|
||||
|
||||
func (r *WorldRepository) SaveKindDefinition(ctx context.Context, value world.WorldObjectKindDefinition) error {
|
||||
if err := worlddomain.ValidateKindDefinition(value); err != nil {
|
||||
return err
|
||||
}
|
||||
var parentOwner, parentKind, parentID string
|
||||
var parentRevision *uint64
|
||||
if value.ParentKindRef != nil {
|
||||
parentOwner, parentKind, parentID = value.ParentKindRef.OwnerNamespace, value.ParentKindRef.SemanticKind, string(value.ParentKindRef.StableID)
|
||||
if value.ParentKindRef.Revision != nil {
|
||||
revision := uint64(*value.ParentKindRef.Revision)
|
||||
parentRevision = &revision
|
||||
}
|
||||
}
|
||||
_, err := r.pool.Exec(ctx, `INSERT INTO world.kind_definitions(kind_definition_id,kind_key,revision,display_name,parent_owner_namespace,parent_semantic_kind,parent_stable_id,parent_revision,lifecycle,definition_digest,recorded_at_unix_millis) VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) ON CONFLICT(kind_definition_id) DO NOTHING`, value.ID, value.KindKey, value.Revision, value.DisplayName, parentOwner, parentKind, parentID, parentRevision, value.Lifecycle, value.DefinitionDigest, value.RecordedAt)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *WorldRepository) SaveObject(ctx context.Context, value world.WorldObject) error {
|
||||
if err := worlddomain.ValidateObject(value); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := r.pool.Exec(ctx, `INSERT INTO world.objects(world_object_id,principal_id,kind_owner_namespace,kind_semantic_kind,kind_stable_id,kind_revision,lifecycle,created_at_unix_millis) VALUES($1,$2,$3,$4,$5,$6,$7,$8) ON CONFLICT(world_object_id) DO NOTHING`, value.ID, value.PrincipalID, value.KindDefinitionRef.OwnerNamespace, value.KindDefinitionRef.SemanticKind, value.KindDefinitionRef.StableID, value.KindRevision, value.Lifecycle, value.CreatedAt)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *WorldRepository) SaveAlias(ctx context.Context, value world.WorldAlias) error {
|
||||
if err := worlddomain.ValidateAlias(value); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := r.pool.Exec(ctx, `INSERT INTO world.aliases(world_alias_id,principal_id,world_object_id,alias,recorded_at_unix_millis) VALUES($1,$2,$3,$4,$5) ON CONFLICT(world_alias_id) DO NOTHING`, value.ID, value.PrincipalID, value.ObjectID, value.Alias, value.RecordedAt)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *WorldRepository) SaveFacetDefinition(ctx context.Context, value world.FacetDefinition) error {
|
||||
if err := worlddomain.ValidateFacetDefinition(value); err != nil {
|
||||
return err
|
||||
}
|
||||
payload, err := json.Marshal(value.ValueType)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encode facet value type: %w", err)
|
||||
}
|
||||
_, err = r.pool.Exec(ctx, `INSERT INTO world.facet_definitions(facet_definition_id,kind_key,revision,facet_key,value_type,definition_digest,recorded_at_unix_millis) VALUES($1,$2,$3,$4,$5,$6,$7) ON CONFLICT(facet_definition_id) DO NOTHING`, value.ID, value.KindKey, value.Revision, value.FacetKey, payload, value.DefinitionDigest, value.RecordedAt)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *WorldRepository) SaveRelationDefinition(ctx context.Context, value world.RelationDefinition) error {
|
||||
if err := worlddomain.ValidateRelationDefinition(value); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := r.pool.Exec(ctx, `INSERT INTO world.relation_definitions(relation_definition_id,kind_key,revision,relation_key,target_kind_key,definition_digest,recorded_at_unix_millis) VALUES($1,$2,$3,$4,$5,$6,$7) ON CONFLICT(relation_definition_id) DO NOTHING`, value.ID, value.KindKey, value.Revision, value.RelationKey, value.TargetKindKey, value.DefinitionDigest, value.RecordedAt)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *WorldRepository) SaveResolution(ctx context.Context, value world.IdentityResolutionDecisionRecord) error {
|
||||
if err := worlddomain.ValidateResolution(value); err != nil {
|
||||
return err
|
||||
}
|
||||
candidates, err := json.Marshal(value.Candidates)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encode resolution candidates: %w", err)
|
||||
}
|
||||
evidence, err := json.Marshal(value.Evidence)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encode resolution evidence: %w", err)
|
||||
}
|
||||
_, err = r.pool.Exec(ctx, `INSERT INTO world.identity_resolution_decisions(resolution_decision_id,principal_id,candidates,decision,evidence,confidence_millis,decided_at_unix_millis,revision) VALUES($1,$2,$3,$4,$5,$6,$7,$8) ON CONFLICT(resolution_decision_id) DO NOTHING`, value.ID, value.PrincipalID, candidates, value.Decision, evidence, value.ConfidenceMillis, value.DecidedAt, value.Revision)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *WorldRepository) SaveLineage(ctx context.Context, value world.WorldLineage) error {
|
||||
if err := worlddomain.ValidateLineage(value); err != nil {
|
||||
return err
|
||||
}
|
||||
sources, err := json.Marshal(value.SourceRefs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encode lineage sources: %w", err)
|
||||
}
|
||||
successors, err := json.Marshal(value.SuccessorRefs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encode lineage successors: %w", err)
|
||||
}
|
||||
_, err = r.pool.Exec(ctx, `INSERT INTO world.lineage(lineage_id,operation,source_refs,successor_refs,recorded_at_unix_millis) VALUES($1,$2,$3,$4,$5) ON CONFLICT(lineage_id) DO NOTHING`, value.ID, value.Operation, sources, successors, value.RecordedAt)
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package configuration
|
||||
|
||||
import "git.cin.su/m/Assistant/platform/ids"
|
||||
|
||||
// SemanticReference is a typed cross-owner reference; settings remain
|
||||
// Configuration-owned records.
|
||||
type SemanticReference = ids.SemanticRef
|
||||
@@ -0,0 +1,7 @@
|
||||
package epistemics
|
||||
|
||||
import "git.cin.su/m/Assistant/platform/ids"
|
||||
|
||||
// SemanticReference identifies evidence-related records without transferring
|
||||
// canonical ownership to Epistemics.
|
||||
type SemanticReference = ids.SemanticRef
|
||||
@@ -0,0 +1,7 @@
|
||||
package self
|
||||
|
||||
import "git.cin.su/m/Assistant/platform/ids"
|
||||
|
||||
// SemanticReference lets SELF cite evidence and capabilities without owning
|
||||
// their canonical state.
|
||||
type SemanticReference = ids.SemanticRef
|
||||
@@ -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,7 @@
|
||||
package situation
|
||||
|
||||
import "git.cin.su/m/Assistant/platform/ids"
|
||||
|
||||
// SemanticReference keeps Situation references transport-neutral while
|
||||
// ContextScope semantics remain owned by Situation.
|
||||
type SemanticReference = ids.SemanticRef
|
||||
@@ -0,0 +1,270 @@
|
||||
package world
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.cin.su/m/Assistant/contracts/identity"
|
||||
"git.cin.su/m/Assistant/platform/clock"
|
||||
"git.cin.su/m/Assistant/platform/ids"
|
||||
)
|
||||
|
||||
// SemanticReference is the transport envelope used by World contracts. It is
|
||||
// a reference only; it does not expose another owner's storage.
|
||||
type SemanticReference = ids.SemanticRef
|
||||
|
||||
type PrincipalRealm string
|
||||
|
||||
const (
|
||||
RealmPersonal PrincipalRealm = "PERSONAL"
|
||||
RealmShared PrincipalRealm = "SHARED"
|
||||
RealmPublic PrincipalRealm = "PUBLIC"
|
||||
RealmSystem PrincipalRealm = "SYSTEM"
|
||||
)
|
||||
|
||||
type SensitivityClass string
|
||||
|
||||
const (
|
||||
SensitivityPublic SensitivityClass = "PUBLIC"
|
||||
SensitivityPrivate SensitivityClass = "PRIVATE"
|
||||
SensitivityRestricted SensitivityClass = "RESTRICTED"
|
||||
SensitivitySecretReference SensitivityClass = "SECRET_REFERENCE"
|
||||
)
|
||||
|
||||
type ValueTypeKind string
|
||||
|
||||
const (
|
||||
ValueString ValueTypeKind = "STRING"
|
||||
ValueInteger ValueTypeKind = "INTEGER"
|
||||
ValueDecimal ValueTypeKind = "DECIMAL"
|
||||
ValueBoolean ValueTypeKind = "BOOLEAN"
|
||||
ValueTime ValueTypeKind = "TIME"
|
||||
ValueDuration ValueTypeKind = "DURATION"
|
||||
ValueEnum ValueTypeKind = "ENUM"
|
||||
ValueReference ValueTypeKind = "REFERENCE"
|
||||
ValueBinaryRef ValueTypeKind = "BINARY_REF"
|
||||
ValueList ValueTypeKind = "LIST"
|
||||
ValueStruct ValueTypeKind = "STRUCT"
|
||||
)
|
||||
|
||||
type ValueField struct {
|
||||
Name string
|
||||
Type ValueTypeSpec
|
||||
}
|
||||
|
||||
type ValueTypeSpec struct {
|
||||
Kind ValueTypeKind
|
||||
Element *ValueTypeSpec
|
||||
Fields []ValueField
|
||||
EnumValues []string
|
||||
ReferenceKind string
|
||||
}
|
||||
|
||||
type WorldObjectID ids.StableID
|
||||
type KindDefinitionID ids.StableID
|
||||
type WorldAliasID ids.StableID
|
||||
type FacetDefinitionID ids.StableID
|
||||
type RelationDefinitionID ids.StableID
|
||||
type ResolutionDecisionID ids.StableID
|
||||
type LineageID ids.StableID
|
||||
|
||||
type LifecycleState string
|
||||
|
||||
const (
|
||||
LifecycleActive LifecycleState = "ACTIVE"
|
||||
LifecycleMerged LifecycleState = "MERGED"
|
||||
LifecycleSplit LifecycleState = "SPLIT"
|
||||
LifecycleRetired LifecycleState = "RETIRED"
|
||||
LifecycleUnknownIdentity LifecycleState = "UNKNOWN_IDENTITY"
|
||||
)
|
||||
|
||||
type WorldObject struct {
|
||||
ID WorldObjectID
|
||||
PrincipalID identity.PrincipalID
|
||||
KindDefinitionRef SemanticReference
|
||||
KindRevision ids.Revision
|
||||
Lifecycle LifecycleState
|
||||
CreatedAt clock.Instant
|
||||
}
|
||||
|
||||
type WorldObjectKindDefinition struct {
|
||||
ID KindDefinitionID
|
||||
KindKey string
|
||||
Revision ids.Revision
|
||||
DisplayName string
|
||||
ParentKindRef *SemanticReference
|
||||
Lifecycle LifecycleState
|
||||
DefinitionDigest string
|
||||
RecordedAt clock.Instant
|
||||
}
|
||||
|
||||
type WorldAlias struct {
|
||||
ID WorldAliasID
|
||||
PrincipalID identity.PrincipalID
|
||||
ObjectID WorldObjectID
|
||||
Alias string
|
||||
RecordedAt clock.Instant
|
||||
}
|
||||
|
||||
type FacetDefinition struct {
|
||||
ID FacetDefinitionID
|
||||
KindKey string
|
||||
Revision ids.Revision
|
||||
FacetKey string
|
||||
ValueType ValueTypeSpec
|
||||
DefinitionDigest string
|
||||
RecordedAt clock.Instant
|
||||
}
|
||||
|
||||
type RelationDefinition struct {
|
||||
ID RelationDefinitionID
|
||||
KindKey string
|
||||
Revision ids.Revision
|
||||
RelationKey string
|
||||
TargetKindKey string
|
||||
DefinitionDigest string
|
||||
RecordedAt clock.Instant
|
||||
}
|
||||
|
||||
type ResolutionDecision string
|
||||
|
||||
const (
|
||||
ResolutionSame ResolutionDecision = "SAME"
|
||||
ResolutionDistinct ResolutionDecision = "DISTINCT"
|
||||
ResolutionMerge ResolutionDecision = "MERGE"
|
||||
ResolutionSplit ResolutionDecision = "SPLIT"
|
||||
ResolutionUnresolved ResolutionDecision = "UNRESOLVED"
|
||||
)
|
||||
|
||||
type IdentityResolutionCandidate struct {
|
||||
ObjectRef SemanticReference
|
||||
Evidence []SemanticReference
|
||||
Confidence int64
|
||||
}
|
||||
|
||||
type IdentityResolutionDecisionRecord struct {
|
||||
ID ResolutionDecisionID
|
||||
PrincipalID identity.PrincipalID
|
||||
Candidates []IdentityResolutionCandidate
|
||||
Decision ResolutionDecision
|
||||
Evidence []SemanticReference
|
||||
ConfidenceMillis int64
|
||||
DecidedAt clock.Instant
|
||||
Revision ids.Revision
|
||||
}
|
||||
|
||||
type WorldLineage struct {
|
||||
ID LineageID
|
||||
Operation ResolutionDecision
|
||||
SourceRefs []SemanticReference
|
||||
SuccessorRefs []SemanticReference
|
||||
RecordedAt clock.Instant
|
||||
}
|
||||
|
||||
func (w WorldObject) Validate() error {
|
||||
if w.ID == "" || !w.KindDefinitionRef.Valid() || !w.KindRevision.Valid() || !validLifecycle(w.Lifecycle) || !w.CreatedAt.Valid() {
|
||||
return fmt.Errorf("world object is incomplete")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k WorldObjectKindDefinition) Validate() error {
|
||||
if k.ID == "" || k.KindKey == "" || !k.Revision.Valid() || k.DisplayName == "" || !validLifecycle(k.Lifecycle) || k.DefinitionDigest == "" || !k.RecordedAt.Valid() {
|
||||
return fmt.Errorf("world kind definition is incomplete")
|
||||
}
|
||||
if k.ParentKindRef != nil && !k.ParentKindRef.Valid() {
|
||||
return fmt.Errorf("world kind parent reference is invalid")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a WorldAlias) Validate() error {
|
||||
if a.ID == "" || a.ObjectID == "" || a.Alias == "" || !a.RecordedAt.Valid() {
|
||||
return fmt.Errorf("world alias is incomplete")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f FacetDefinition) Validate() error {
|
||||
if f.ID == "" || f.KindKey == "" || !f.Revision.Valid() || f.FacetKey == "" || f.DefinitionDigest == "" || !f.RecordedAt.Valid() {
|
||||
return fmt.Errorf("facet definition is incomplete")
|
||||
}
|
||||
return f.ValueType.Validate()
|
||||
}
|
||||
|
||||
func (r RelationDefinition) Validate() error {
|
||||
if r.ID == "" || r.KindKey == "" || !r.Revision.Valid() || r.RelationKey == "" || r.TargetKindKey == "" || r.DefinitionDigest == "" || !r.RecordedAt.Valid() {
|
||||
return fmt.Errorf("relation definition is incomplete")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d IdentityResolutionDecisionRecord) Validate() error {
|
||||
if d.ID == "" || !validDecision(d.Decision) || len(d.Candidates) == 0 || d.ConfidenceMillis < 0 || !d.DecidedAt.Valid() || !d.Revision.Valid() {
|
||||
return fmt.Errorf("identity resolution decision is incomplete")
|
||||
}
|
||||
for _, candidate := range d.Candidates {
|
||||
if !candidate.ObjectRef.Valid() || candidate.Confidence < 0 || candidate.Confidence > 1000 {
|
||||
return fmt.Errorf("identity resolution candidate is invalid")
|
||||
}
|
||||
for _, evidence := range candidate.Evidence {
|
||||
if !evidence.Valid() {
|
||||
return fmt.Errorf("identity resolution evidence reference is invalid")
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l WorldLineage) Validate() error {
|
||||
if l.ID == "" || (l.Operation != ResolutionMerge && l.Operation != ResolutionSplit) || len(l.SourceRefs) == 0 || len(l.SuccessorRefs) == 0 || !l.RecordedAt.Valid() {
|
||||
return fmt.Errorf("world lineage is incomplete")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validLifecycle(value LifecycleState) bool {
|
||||
return value == LifecycleActive || value == LifecycleMerged || value == LifecycleSplit || value == LifecycleRetired || value == LifecycleUnknownIdentity
|
||||
}
|
||||
|
||||
func validDecision(value ResolutionDecision) bool {
|
||||
return value == ResolutionSame || value == ResolutionDistinct || value == ResolutionMerge || value == ResolutionSplit || value == ResolutionUnresolved
|
||||
}
|
||||
|
||||
func (s ValueTypeSpec) Validate() error {
|
||||
if s.Kind == "" {
|
||||
return fmt.Errorf("value type kind is required")
|
||||
}
|
||||
switch s.Kind {
|
||||
case ValueList:
|
||||
if s.Element == nil {
|
||||
return fmt.Errorf("list element type is required")
|
||||
}
|
||||
return s.Element.Validate()
|
||||
case ValueStruct:
|
||||
if len(s.Fields) == 0 {
|
||||
return fmt.Errorf("struct fields are required")
|
||||
}
|
||||
seen := map[string]struct{}{}
|
||||
for _, field := range s.Fields {
|
||||
if field.Name == "" {
|
||||
return fmt.Errorf("struct field name is required")
|
||||
}
|
||||
if _, ok := seen[field.Name]; ok {
|
||||
return fmt.Errorf("duplicate struct field %q", field.Name)
|
||||
}
|
||||
seen[field.Name] = struct{}{}
|
||||
if err := field.Type.Validate(); err != nil {
|
||||
return fmt.Errorf("field %s: %w", field.Name, err)
|
||||
}
|
||||
}
|
||||
case ValueEnum:
|
||||
if len(s.EnumValues) == 0 {
|
||||
return fmt.Errorf("enum values are required")
|
||||
}
|
||||
case ValueReference:
|
||||
if s.ReferenceKind == "" {
|
||||
return fmt.Errorf("reference kind is required")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package world
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestValueTypeSpecValidatesTypedRecursiveShape(t *testing.T) {
|
||||
spec := ValueTypeSpec{Kind: ValueStruct, Fields: []ValueField{{Name: "name", Type: ValueTypeSpec{Kind: ValueString}}, {Name: "refs", Type: ValueTypeSpec{Kind: ValueList, Element: &ValueTypeSpec{Kind: ValueReference, ReferenceKind: "world.object"}}}}}
|
||||
if err := spec.Validate(); err != nil {
|
||||
t.Fatalf("ValueTypeSpec.Validate() = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueTypeSpecRejectsDuplicateFields(t *testing.T) {
|
||||
spec := ValueTypeSpec{Kind: ValueStruct, Fields: []ValueField{{Name: "same", Type: ValueTypeSpec{Kind: ValueString}}, {Name: "same", Type: ValueTypeSpec{Kind: ValueString}}}}
|
||||
if err := spec.Validate(); err == nil {
|
||||
t.Fatal("duplicate fields were accepted")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package world
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.cin.su/m/Assistant/platform/clock"
|
||||
"git.cin.su/m/Assistant/platform/ids"
|
||||
)
|
||||
|
||||
func TestWorldObjectValidationKeepsReferentSeparateFromClaims(t *testing.T) {
|
||||
object := WorldObject{ID: "object-1", KindDefinitionRef: ids.SemanticRef{OwnerNamespace: "world", SemanticKind: "kind", StableID: "kind-1"}, KindRevision: 1, Lifecycle: LifecycleActive, CreatedAt: clock.Instant(1)}
|
||||
if err := object.Validate(); err != nil {
|
||||
t.Fatalf("WorldObject.Validate() = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIdentityResolutionRequiresEvidenceBackedCandidates(t *testing.T) {
|
||||
decision := IdentityResolutionDecisionRecord{ID: "decision-1", Decision: ResolutionSame, Candidates: []IdentityResolutionCandidate{{ObjectRef: ids.SemanticRef{OwnerNamespace: "world", SemanticKind: "object", StableID: "object-1"}, Confidence: 900}}, DecidedAt: clock.Instant(1), Revision: 1}
|
||||
if err := decision.Validate(); err != nil {
|
||||
t.Fatalf("IdentityResolutionDecisionRecord.Validate() = %v", err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
BEGIN;
|
||||
|
||||
CREATE SCHEMA IF NOT EXISTS world;
|
||||
SET ROLE world_role;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS world.kind_definitions (
|
||||
kind_definition_id TEXT PRIMARY KEY,
|
||||
kind_key TEXT NOT NULL,
|
||||
revision BIGINT NOT NULL CHECK (revision > 0),
|
||||
display_name TEXT NOT NULL,
|
||||
parent_owner_namespace TEXT NOT NULL DEFAULT '',
|
||||
parent_semantic_kind TEXT NOT NULL DEFAULT '',
|
||||
parent_stable_id TEXT NOT NULL DEFAULT '',
|
||||
parent_revision BIGINT,
|
||||
lifecycle TEXT NOT NULL,
|
||||
definition_digest TEXT NOT NULL,
|
||||
recorded_at_unix_millis BIGINT NOT NULL CHECK (recorded_at_unix_millis > 0),
|
||||
UNIQUE (kind_key, revision)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS world.objects (
|
||||
world_object_id TEXT PRIMARY KEY,
|
||||
principal_id TEXT,
|
||||
kind_owner_namespace TEXT NOT NULL,
|
||||
kind_semantic_kind TEXT NOT NULL,
|
||||
kind_stable_id TEXT NOT NULL,
|
||||
kind_revision BIGINT,
|
||||
lifecycle TEXT NOT NULL,
|
||||
created_at_unix_millis BIGINT NOT NULL CHECK (created_at_unix_millis > 0)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS world.aliases (
|
||||
world_alias_id TEXT PRIMARY KEY,
|
||||
principal_id TEXT,
|
||||
world_object_id TEXT NOT NULL,
|
||||
alias TEXT NOT NULL,
|
||||
recorded_at_unix_millis BIGINT NOT NULL CHECK (recorded_at_unix_millis > 0)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS world.facet_definitions (
|
||||
facet_definition_id TEXT PRIMARY KEY,
|
||||
kind_key TEXT NOT NULL,
|
||||
revision BIGINT NOT NULL CHECK (revision > 0),
|
||||
facet_key TEXT NOT NULL,
|
||||
value_type JSONB NOT NULL,
|
||||
definition_digest TEXT NOT NULL,
|
||||
recorded_at_unix_millis BIGINT NOT NULL CHECK (recorded_at_unix_millis > 0),
|
||||
UNIQUE (kind_key, facet_key, revision)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS world.relation_definitions (
|
||||
relation_definition_id TEXT PRIMARY KEY,
|
||||
kind_key TEXT NOT NULL,
|
||||
revision BIGINT NOT NULL CHECK (revision > 0),
|
||||
relation_key TEXT NOT NULL,
|
||||
target_kind_key TEXT NOT NULL,
|
||||
definition_digest TEXT NOT NULL,
|
||||
recorded_at_unix_millis BIGINT NOT NULL CHECK (recorded_at_unix_millis > 0),
|
||||
UNIQUE (kind_key, relation_key, revision)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS world.identity_resolution_decisions (
|
||||
resolution_decision_id TEXT PRIMARY KEY,
|
||||
principal_id TEXT,
|
||||
candidates JSONB NOT NULL,
|
||||
decision TEXT NOT NULL,
|
||||
evidence JSONB NOT NULL,
|
||||
confidence_millis BIGINT NOT NULL CHECK (confidence_millis >= 0),
|
||||
decided_at_unix_millis BIGINT NOT NULL CHECK (decided_at_unix_millis > 0),
|
||||
revision BIGINT NOT NULL CHECK (revision > 0)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS world.lineage (
|
||||
lineage_id TEXT PRIMARY KEY,
|
||||
operation TEXT NOT NULL,
|
||||
source_refs JSONB NOT NULL,
|
||||
successor_refs JSONB NOT NULL,
|
||||
recorded_at_unix_millis BIGINT NOT NULL CHECK (recorded_at_unix_millis > 0)
|
||||
);
|
||||
|
||||
RESET ROLE;
|
||||
COMMIT;
|
||||
@@ -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-001-accept-gen2-owner-registry-and-dependency-rules.json",
|
||||
"task_id": "GEN2-001",
|
||||
"task_revision": 1
|
||||
}
|
||||
{"task_file":"dev/tasks/GEN2-004-replace-project-centric-focus-with-contextscope-focusframe-focusstack.json","task_id":"GEN2-004","task_revision":1}
|
||||
@@ -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,8 @@
|
||||
# world
|
||||
|
||||
World owns typed referents, vocabulary and identity-resolution lineage. It
|
||||
does not own observations, claims, beliefs, or factual values about a
|
||||
referent. Those remain Epistemics-owned evidence.
|
||||
|
||||
World domain code does not import adapters, runtime or provider packages and
|
||||
does not write another owner's canonical state.
|
||||
@@ -0,0 +1,17 @@
|
||||
package domain
|
||||
|
||||
import "git.cin.su/m/Assistant/contracts/world"
|
||||
|
||||
func ValidateObject(value world.WorldObject) error { return value.Validate() }
|
||||
|
||||
func ValidateKindDefinition(value world.WorldObjectKindDefinition) error { return value.Validate() }
|
||||
|
||||
func ValidateAlias(value world.WorldAlias) error { return value.Validate() }
|
||||
|
||||
func ValidateFacetDefinition(value world.FacetDefinition) error { return value.Validate() }
|
||||
|
||||
func ValidateRelationDefinition(value world.RelationDefinition) error { return value.Validate() }
|
||||
|
||||
func ValidateResolution(value world.IdentityResolutionDecisionRecord) error { return value.Validate() }
|
||||
|
||||
func ValidateLineage(value world.WorldLineage) error { return value.Validate() }
|
||||
@@ -0,0 +1,17 @@
|
||||
package ports
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.cin.su/m/Assistant/contracts/world"
|
||||
)
|
||||
|
||||
type Repository interface {
|
||||
SaveKindDefinition(context.Context, world.WorldObjectKindDefinition) error
|
||||
SaveObject(context.Context, world.WorldObject) error
|
||||
SaveAlias(context.Context, world.WorldAlias) error
|
||||
SaveFacetDefinition(context.Context, world.FacetDefinition) error
|
||||
SaveRelationDefinition(context.Context, world.RelationDefinition) error
|
||||
SaveResolution(context.Context, world.IdentityResolutionDecisionRecord) error
|
||||
SaveLineage(context.Context, world.WorldLineage) error
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package clock
|
||||
|
||||
import "time"
|
||||
|
||||
// Instant is a UTC point represented as Unix milliseconds.
|
||||
type Instant int64
|
||||
|
||||
func (i Instant) Valid() bool { return i > 0 }
|
||||
|
||||
func NowInstant() Instant { return FromTime(time.Now().UTC()) }
|
||||
|
||||
func FromTime(value time.Time) Instant { return Instant(value.UTC().UnixMilli()) }
|
||||
|
||||
func (i Instant) Time() time.Time { return time.UnixMilli(int64(i)).UTC() }
|
||||
@@ -0,0 +1,14 @@
|
||||
package clock
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestInstantRoundTripUsesUTCUnixMilliseconds(t *testing.T) {
|
||||
want := time.Date(2026, time.August, 29, 12, 34, 56, 789000000, time.FixedZone("local", 3*60*60))
|
||||
got := FromTime(want).Time()
|
||||
if !got.Equal(want.UTC()) || got.Location() != time.UTC {
|
||||
t.Fatalf("instant round trip = %v, want %v UTC", got, want.UTC())
|
||||
}
|
||||
}
|
||||
+34
-5
@@ -6,14 +6,43 @@ import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ID is a transport-neutral opaque identifier primitive. Domain packages wrap it
|
||||
// in strong semantic ID types; do not pass bare ID across semantic boundaries.
|
||||
type ID string
|
||||
// StableID is a transport-neutral opaque identifier primitive. Domain packages
|
||||
// wrap it in strong semantic ID types; do not pass bare StableID across
|
||||
// semantic boundaries.
|
||||
type StableID string
|
||||
|
||||
func New() (ID, error) {
|
||||
// ID is retained as a compatibility name for the V1 primitive.
|
||||
type ID = StableID
|
||||
|
||||
// Revision is the optimistic version of a versioned semantic record.
|
||||
type Revision uint64
|
||||
|
||||
const InitialRevision Revision = 1
|
||||
|
||||
func (r Revision) Valid() bool { return r >= InitialRevision }
|
||||
|
||||
// SemanticRef is a technical cross-owner reference envelope. It identifies a
|
||||
// record but does not grant storage ownership or provide generic CRUD.
|
||||
type SemanticRef struct {
|
||||
OwnerNamespace string
|
||||
SemanticKind string
|
||||
StableID StableID
|
||||
Revision *Revision
|
||||
}
|
||||
|
||||
func (r SemanticRef) Valid() bool {
|
||||
return r.OwnerNamespace != "" && r.SemanticKind != "" && r.StableID != "" &&
|
||||
(r.Revision == nil || r.Revision.Valid())
|
||||
}
|
||||
|
||||
func NewStableID() (StableID, error) {
|
||||
var b [16]byte
|
||||
if _, err := rand.Read(b[:]); err != nil {
|
||||
return "", fmt.Errorf("generate id: %w", err)
|
||||
}
|
||||
return ID(hex.EncodeToString(b[:])), nil
|
||||
return StableID(hex.EncodeToString(b[:])), nil
|
||||
}
|
||||
|
||||
func New() (ID, error) {
|
||||
return NewStableID()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package ids
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestNewStableIDIsOpaque128BitHex(t *testing.T) {
|
||||
id, err := NewStableID()
|
||||
if err != nil || len(id) != 32 {
|
||||
t.Fatalf("NewStableID() = %q, %v", id, err)
|
||||
}
|
||||
if !(SemanticRef{OwnerNamespace: "world", SemanticKind: "object", StableID: id}.Valid()) {
|
||||
t.Fatal("stable id was not accepted by semantic reference")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSemanticRefRejectsInvalidRevision(t *testing.T) {
|
||||
revision := Revision(0)
|
||||
ref := SemanticRef{OwnerNamespace: "world", SemanticKind: "object", StableID: "id", Revision: &revision}
|
||||
if ref.Valid() {
|
||||
t.Fatal("semantic reference accepted invalid revision")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user