Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8037e66998 | ||
|
|
29815c49b8 |
@@ -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,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;
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"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-003-add-narrow-world-owner-and-typed-vocabulary.json",
|
||||
"task_revision": 1,
|
||||
"task_id": "GEN2-003"
|
||||
}
|
||||
@@ -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