Compare commits

..
Author SHA1 Message Date
m 8037e66998 GEN2-003: add typed World referent foundation
CI / test (push) Successful in 50s
2026-08-29 02:11:36 +03:00
8 changed files with 431 additions and 4 deletions
+108
View File
@@ -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
}
+173
View File
@@ -3,6 +3,8 @@ 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"
)
@@ -57,6 +59,177 @@ type ValueTypeSpec struct {
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")
+22
View File
@@ -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;
+4 -4
View File
@@ -1,5 +1,5 @@
{
"task_file": "dev/tasks/GEN2-002-introduce-shared-gen2-data-primitives-without-shared-domain-semantics.json",
"task_id": "GEN2-002",
"task_revision": 1
}
"task_file": "dev/tasks/GEN2-003-add-narrow-world-owner-and-typed-vocabulary.json",
"task_revision": 1,
"task_id": "GEN2-003"
}
+8
View File
@@ -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.
+17
View File
@@ -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() }
+17
View File
@@ -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
}