Index: flexoentity/domain.py
===================================================================
--- flexoentity/domain.py	(revision 4dc09bb21c2427aac48dc327be127f925d71e45e)
+++ flexoentity/domain.py	(revision 6ad031bcb95bd1b1d329074886e49c72ec5d46e3)
@@ -1,5 +1,5 @@
+from uuid import UUID
 from dataclasses import dataclass
 from flexoentity import FlexOID, FlexoEntity, EntityType, EntityState
-
 
 @dataclass
@@ -9,6 +9,7 @@
     domain abbreviation in FlexOID, doing mapping and management
     """
+
     ENTITY_TYPE = EntityType.DOMAIN
-    subtype = "GENERAL"
+
     fullname: str = ""
     description: str = ""
@@ -17,17 +18,16 @@
     @classmethod
     def default(cls):
-        """I return an default instance of myself"""
-        return cls("GENERIC")
+        """Return the default domain object."""
+        return cls(fullname="Generic Domain", classification="UNCLASSIFIED")
 
     def __post_init__(self):
         super().__post_init__()
-
-        # Generate FlexOID only if missing
+        # Create a FlexOID only if none was restored
         if not getattr(self, "flexo_id", None):
             self.flexo_id = FlexOID.safe_generate(
-                domain="GENERAL",
-                entity_type=EntityType.DOMAIN.value,   # 'D'
-                estate=EntityState.DRAFT.value,      # 'D'
-                text=self.text_seed,
+                domain_id=self.domain_id,
+                entity_type=self.ENTITY_TYPE.value,   # "D"
+                state=EntityState.DRAFT.value,       # "D"
+                text=self.domain_id,                       # seed must be stable
                 version=1,
             )
@@ -35,21 +35,10 @@
     @property
     def text_seed(self) -> str:
-        """
-        I provide a deterministic text seed for ID generation.
-        FIXME: There might be a better text seed, json probably
-        """
-        return f"{self.fullname}|{self.classification}|{self.owner_id}"
-
-    @property
-    def domain_code(self) -> str:
-        """
-        I am self-domaining — I use my own subtype as domain code.
-        """
-        return self.subtype or "GENERAL"
+        return self.domain_id
 
     def to_dict(self):
-        """I return a dictionary representing my state"""
         base = super().to_dict()
         base.update({
+            "domain_id": self.domain_id,
             "fullname": self.fullname,
             "description": self.description,
@@ -60,8 +49,27 @@
     @classmethod
     def from_dict(cls, data):
-        """I return a new instance of myself, created from state provided by a dictionary"""
-        return cls(
+        # Must have flexo_id
+        if "flexo_id" not in data:
+            raise ValueError("Domain serialization missing 'flexo_id'.")
+
+        flexo_id = FlexOID(data["flexo_id"])
+
+        print("Data:", data)
+        obj = cls(
             fullname=data.get("fullname", ""),
             description=data.get("description", ""),
             classification=data.get("classification", "UNCLASSIFIED"),
+            flexo_id=flexo_id,
         )
+
+        # Restore metadata
+        obj.origin = data.get("origin")
+        obj.fingerprint = data.get("fingerprint", "")
+        obj.originator_id = (
+            UUID(data["originator_id"]) if data.get("originator_id") else None
+        )
+        obj.owner_id = (
+            UUID(data["owner_id"]) if data.get("owner_id") else None
+        )
+
+        return obj
Index: flexoentity/domain_manager.py
===================================================================
--- flexoentity/domain_manager.py	(revision 6ad031bcb95bd1b1d329074886e49c72ec5d46e3)
+++ flexoentity/domain_manager.py	(revision 6ad031bcb95bd1b1d329074886e49c72ec5d46e3)
@@ -0,0 +1,97 @@
+from typing import Dict, Optional
+from flexoentity import FlexOID, EntityType, EntityState
+from .domain import Domain
+
+
+class DomainManager:
+    """
+    I manage all Domain instances in the system.
+
+    Responsibilities:
+        • ensure unique domain codes
+        • provide lookup by code and by FlexOID
+        • restore domains from dict/json safely
+        • serve as the canonical source of domain objects
+    """
+
+    _by_code: Dict[str, Domain] = {}
+    _by_oid: Dict[str, Domain] = {}
+
+    # ---------------------------------------------------------------
+    # Registration
+    # ---------------------------------------------------------------
+    @classmethod
+    def register(cls, domain: Domain):
+        """Register a domain; raise error if code already exists."""
+        code = domain.domain_id
+        oid = str(domain.flexo_id)
+
+        if code in cls._by_code:
+            raise ValueError(f"Domain code already registered: {code}")
+
+        cls._by_code[code] = domain
+        cls._by_oid[oid] = domain
+        return domain
+
+    @classmethod
+    def register_or_replace(cls, domain: Domain):
+        """Replace an existing domain with same code — rarely useful, but optional."""
+        code = domain.code
+        oid = str(domain.flexo_id)
+        cls._by_code[code] = domain
+        cls._by_oid[oid] = domain
+        return domain
+
+    @classmethod
+    def get(cls, code: str) -> Domain:
+        """Return the domain for given code; error if missing."""
+        if code not in cls._by_code:
+            raise KeyError(f"Unknown domain code: {code}")
+        return cls._by_code[code]
+
+    # ---------------------------------------------------------------
+    # Lookup helpers
+    # ---------------------------------------------------------------
+    @classmethod
+    def get_by_oid(cls, oid) -> Domain:
+        """Return the domain with the given FlexOID string or object."""
+        key = str(oid)
+        if key not in cls._by_oid:
+            raise KeyError(f"Unknown domain OID: {key}")
+        return cls._by_oid[key]
+
+    # ---------------------------------------------------------------
+    # Creation helpers
+    # ---------------------------------------------------------------
+    @classmethod
+    def create(cls, domain_id: str, **kwargs) -> Domain:
+        """
+        Create a new domain, register it, and return it.
+        Raises if code exists.
+        """
+        flexo_id = FlexOID.safe_generate(domain_id=domain_id,
+                                         entity_type=EntityType.DOMAIN.value,
+                                         state=EntityState.DRAFT.value, text=domain_id)
+        domain = Domain(flexo_id=flexo_id, **kwargs)
+        return cls.register(domain)
+
+    @classmethod
+    def get_or_create(cls, code: str, **kwargs) -> Domain:
+        """Return existing domain or create+register new one."""
+        if code in cls._by_code:
+            return cls._by_code[code]
+        return cls.create(code, **kwargs)
+
+    # ---------------------------------------------------------------
+    # Utility
+    # ---------------------------------------------------------------
+    @classmethod
+    def list(cls):
+        """Return all domain objects."""
+        return list(cls._by_code.values())
+
+    @classmethod
+    def clear(cls):
+        """Clear registry — useful for tests."""
+        cls._by_code.clear()
+        cls._by_oid.clear()
Index: flexoentity/flexo_entity.py
===================================================================
--- flexoentity/flexo_entity.py	(revision 4dc09bb21c2427aac48dc327be127f925d71e45e)
+++ flexoentity/flexo_entity.py	(revision 6ad031bcb95bd1b1d329074886e49c72ec5d46e3)
@@ -11,5 +11,4 @@
 - My `flexo_id` is the canonical source of truth.
   It deterministically encodes:
-    - domain      (prefix)
     - entity type (single letter, e.g. I, C, M)
     - date + hash (prefix stability)
@@ -25,7 +24,4 @@
     - originator_id identifies the original creator (UUID)
     - owner_id identifies the current responsible user or process (UUID)
-
-- My domain field is a semantic hint, useful for filtering or
-  multi-tenant setups, but the canonical domain remains encoded in my ID.
 
 Design principles
@@ -144,5 +140,4 @@
     I am the living, editable layer that connects identity with accountability.
     """
-
     subtype: str = "GENERIC"
     flexo_id: Optional[FlexOID] = field(default=None)
@@ -165,4 +160,9 @@
 
     @property
+    def domain_id(self):
+        """I return my domain_id derived from FlexOID"""
+        return self.flexo_id.domain_id
+
+    @property
     def state(self) -> EntityState:
         """I return the state derived from my FlexOID"""
@@ -187,26 +187,17 @@
         raise NotImplementedError("Subclasses must implement default()")
 
-    @property
-    def domain_code(self):
-        return self.flexo_id.domain
-
-    @property
-    def default_domain_code(self):
-        return "GENERAL"
-
     @classmethod
-    def with_domain(cls, domain, **kwargs):
-        """
-        I create an entity in a specific domain.
-        """
-
-        etype = getattr(cls, "ENTITY_TYPE", None)
-        if not etype:
+    def with_domain_id(cls, domain_id: str, **kwargs):
+        from .domain_manager import DomainManager
+        entity_type = getattr(cls, "ENTITY_TYPE", None)
+        if not entity_type:
             raise ValueError(f"{cls.__name__} must define ENTITY_TYPE")
 
+        DomainManager.get_or_create(domain_id)  # ensure registered
+
         flexo_id = FlexOID.safe_generate(
-            domain=domain,
-            entity_type=etype.value,
-            estate=EntityState.DRAFT.value,
+            domain_id=domain_id,
+            entity_type=entity_type.value,
+            state="D",
             text=kwargs.get("text_seed", ""),
             version=1,
@@ -219,26 +210,29 @@
     def __post_init__(self):
         """
-        Optionally generate a new FlexOID and fingerprint if none exist.
-
-        I check for subclass attribute ENTITY_TYPE.
-        If it exist, I use it to generate a draft FlexOID.
-        Otherwise, I skip ID generation — leaving it to the subclass.
-        """
-
-        # If already has a FlexOID, do nothing
-        if getattr(self, "flexo_id", None):
+        FINAL LOGIC:
+            - If flexo_id exists → restore domain_id via DomainManager.
+            - If flexo_id missing → create via safe_generate.
+            - Domain must always match flexo_id.domain.
+        """
+
+        # Step 1: If flexo_id exists, restore canonical domain
+        if self.flexo_id:
             return
-
-        # Skip if subclass doesn’t declare ENTITY_TYPE or initial state
-        etype = getattr(self.__class__, "ENTITY_TYPE", None)
-        if not etype:
-            # No info available, do nothing
-            return
-
-        # Generate a new draft FlexOID
+        # Step 2: flexo_id missing → generate one
+        entity_type = getattr(self.__class__, "ENTITY_TYPE", None)
+        if not entity_type:
+            raise ValueError(f"{self.__class__.__name__} must define ENTITY_TYPE")
+
+        if not self.domain_id:
+            raise ValueError(
+                f"{self.__class__.__name__} created without domain; "
+                f"use {self.__class__.__name__}.with_domain(domain=...) or pass domain explicitly."
+            )
+
+        # Create identity
         self.flexo_id = FlexOID.safe_generate(
-            domain=self.default_domain_code,
-            entity_type=etype.value,
-            estate=EntityState.DRAFT.value,
+            domain_id=self.domain_id,
+            entity_type=entity_type.value,
+            state=EntityState.DRAFT.value,
             text=self.text_seed,
             version=1,
@@ -255,27 +249,43 @@
 
     def to_dict(self):
+        """I return the a dictionary representation of myself"""
         return {
-            "domain": self.domain_code,
-            "entity_type": self.entity_type.name,
-            "state": self.state.name,
             "flexo_id": str(self.flexo_id),
+            "domain_id": self.domain_id,
             "fingerprint": self.fingerprint,
             "origin": self.origin,
             "originator_id": str(self.originator_id),
-            "owner_id": str(self.owner_id)
+            "owner_id": str(self.owner_id),
         }
 
     @classmethod
     def from_dict(cls, data):
-        from flexoentity.domain import Domain  # avoid circular import
-        domain_obj = Domain(data.get("domain", "GENERIC"))
+        """
+        FINAL VERSION:
+        - NEVER generates a new FlexOID.
+        - NEVER calls with_domain().
+        - ALWAYS restores the canonical Domain via DomainManager.
+        """
+        if "flexo_id" not in data:
+            raise ValueError("Serialized entity must include 'flexo_id'.")
+
+        flexo_id = FlexOID(data["flexo_id"])
+
+        # canonical domain object
+
         obj = cls(
-            domain=domain_obj,
-        )
-        obj.flexo_id = FlexOID(data["flexo_id"])
+            flexo_id=flexo_id,
+        )
+
+        # restore provenance
         obj.fingerprint = data.get("fingerprint", "")
         obj.origin = data.get("origin")
-        obj.originator_id = UUID(int=int(data.get("originator_id", "0")))
-        obj.owner_id = UUID(int=int(data.get("owner_id", "0")))
+
+        if data.get("originator_id"):
+            obj.originator_id = UUID(data["originator_id"])
+
+        if data.get("owner_id"):
+            obj.owner_id = UUID(data["owner_id"])
+
         return obj
 
@@ -320,5 +330,5 @@
         if new_fp != self.fingerprint:
             self.fingerprint = new_fp
-            self.flexo_id = FlexOID.safe_generate(self.domain_code,
+            self.flexo_id = FlexOID.safe_generate(self.domain_id,
                                                   self.entity_type.value,
                                                   self.state.value,
@@ -385,5 +395,5 @@
         """
         if self.state == EntityState.DRAFT:
-            new_fid = FlexOID.safe_generate(self.domain_code,
+            new_fid = FlexOID.safe_generate(self.domain_id,
                                             self.entity_type.value,
                                             EntityState.APPROVED.value,
@@ -424,5 +434,5 @@
 
         new_fid = FlexOID.safe_generate(
-            self.domain_code,
+            self.domain_id,
             self.entity_type.value,
             EntityState.PUBLISHED.value,
@@ -436,4 +446,5 @@
 
     def obsolete(self):
+        """I mark myself as obsolete"""
         allowed = self.allowed_transitions()
         if "OBSOLETE" not in allowed:
@@ -450,5 +461,5 @@
         self.origin = str(self.flexo_id)
         self.flexo_id = FlexOID.clone_new_base(
-            self.domain_code,
+            self.domain_id,
             self.entity_type.value,
             EntityState.DRAFT.value,
@@ -461,46 +472,4 @@
     # ───────────────────────────────────────────────────────────────
     @staticmethod
-    def debug_integrity(entity):
-        """
-        Return a dict with all values used by verify_integrity so we can see
-        exactly why a check passes/fails.
-        """
-        info = {}
-        try:
-            fid = entity.flexo_id
-            info["fid"] = str(fid)
-            info["fid_domain"] = fid.domain
-            info["fid_type"] = fid.entity_type
-            info["fid_state"] = fid.state_code
-            info["fid_hash_part"] = fid.hash_part
-            info["version"] = fid.version
-
-            # Derived views
-            info["derived_domain_code"] = getattr(entity, "domain_code")() if hasattr(entity, "domain_code") else None
-            info["derived_entity_type_value"] = entity.entity_type.value
-            info["derived_state_value"] = entity.state.value
-
-            # Seeds & fingerprints
-            info["text_seed"] = entity.text_seed
-            exp_fp = entity._compute_fingerprint()
-            info["expected_fingerprint"] = exp_fp
-            info["stored_fingerprint"] = getattr(entity, "fingerprint", None)
-
-            # Expected ID hash from current seed (should match fid.hash_part
-            # if content didn't change)
-            exp_hash = FlexOID._blake_hash(canonical_seed(f"{fid.domain}:{fid.entity_type}:{entity.text_seed}"))
-            info["expected_id_hash_from_seed"] = exp_hash
-
-            # Quick mismatches
-            info["mismatch_fp"] = (info["stored_fingerprint"] != exp_fp)
-            info["mismatch_hash"] = (fid.hash_part != exp_hash)
-            info["mismatch_type"] = (entity.entity_type.value != fid.entity_type)
-            info["mismatch_state"] = (entity.state.value != fid.state_code)
-            info["mismatch_domain"] = (info["derived_domain_code"] is not None and info["derived_domain_code"] != fid.domain)
-        except Exception as e:
-            info["error"] = repr(e)
-        return info
-
-    @staticmethod
     def verify_integrity(entity) -> bool:
         """
@@ -517,5 +486,5 @@
 
             # Validate domain and ID coherence
-            if entity.domain_code != entity.flexo_id.domain:
+            if entity.domain_id != entity.flexo_id.domain_id:
                 return False
             if entity.entity_type.value != entity.flexo_id.entity_type:
Index: flexoentity/id_factory.py
===================================================================
--- flexoentity/id_factory.py	(revision 4dc09bb21c2427aac48dc327be127f925d71e45e)
+++ flexoentity/id_factory.py	(revision 6ad031bcb95bd1b1d329074886e49c72ec5d46e3)
@@ -8,5 +8,5 @@
 Each Flex-O ID (FlexOID) is a self-contained string that encodes:
 
-    <DOMAIN>-<ETYPE><YYMMDD>-<HASH>@<VERSION><STATE>
+    <DOMAIN_ID>-<ENTITY_TYPE><YYMMDD>-<HASH>@<VERSION><STATE>
 
 Example:
@@ -14,6 +14,6 @@
 
 where
-    DOMAIN      — a short prefix identifying the origin domain (e.g., GEN)
-    ETYPE       — a compact entity type code (e.g., ITEM)
+    DOMAIN_ID   — a prefix identifying the origin domain (e.g., PY_ARITHM)
+    ENTITY_TYPE — a compact entity type single letter code (e.g., "I" for ITEM)
     YYMMDD      — the UTC creation date
     HASH        — a 12-hex BLAKE2s digest derived from canonical content
@@ -56,5 +56,5 @@
 
 • Validation:  Every FlexOID validates itself through a strict regex pattern
-               and exposes lightweight accessors (domain, type, date, hash,
+               and exposes lightweight accessors (domain_id, type, date, hash,
                version, state).
 
@@ -114,6 +114,6 @@
 
     OID_PATTERN = re.compile(
-        r"^(?P<domain>[A-Z0-9_]+)-"
-        r"(?P<etype>[A-Z0-9]+)"
+        r"^(?P<domain_id>[A-Z0-9_]+)-"
+        r"(?P<entity_type>[A-Z0-9]+)"
         r"(?P<date>\d{6})-"
         r"(?P<hash>[A-F0-9]+)@"
@@ -121,4 +121,64 @@
         r"(?P<state>[A-Z])$"
     )
+    @classmethod
+    def from_strings(cls, domain_id, entity_type, date, hash_part, version, state):
+        """
+        Construct a FlexOID from raw string components only.
+        All parameters must already be strings in the exact expected format.
+        No coercions, no guesses, no defaults.
+
+        Required formats:
+           domain_id:    [A-Z0-9_]+
+           entity_type:  [A-Z]
+           date:         YYMMDD (6 digits)
+           hash_part:    uppercase hex, length >= 1
+           version:      001..999 as 3-digit string
+           state:        single capital letter
+        """
+
+        # Domain format
+        if not isinstance(domain_id, str) or not re.fullmatch(r"[A-Z0-9_]+", domain_id):
+            raise ValueError(f"Invalid domain_id string: {domain_id}")
+
+        # Entity type format
+        if not isinstance(entity_type, str) or not re.fullmatch(r"[A-Z]", entity_type):
+            raise ValueError(f"Invalid entity_type string: {entity_type}")
+
+        # Date format
+        if not isinstance(date, str) or not re.fullmatch(r"\d{6}", date):
+            raise ValueError(f"Invalid date string: {date}")
+
+        # Hash format
+        if not isinstance(hash_part, str) or not re.fullmatch(r"[A-F0-9]+", hash_part):
+            raise ValueError(f"Invalid hash string: {hash_part}")
+
+        # Version
+        if not isinstance(version, str) or not re.fullmatch(r"\d{3}", version):
+            raise ValueError(f"Invalid version string: {version}")
+
+        version_int = int(version)
+        if not (1 <= version_int <= cls.MAX_VERSION):
+            raise ValueError(f"Version {version} out of range.")
+
+        # State
+        if not isinstance(state, str) or not re.fullmatch(r"[A-Z]", state):
+            raise ValueError(f"Invalid state: {state}")
+
+        oid_str = f"{domain_id}-{entity_type}{date}-{hash_part}@{version}{state}"
+        return cls(oid_str)
+
+    @classmethod
+    def from_dict(cls, d):
+        try:
+            return cls.from_strings(
+                domain_id=d["domain_id"],
+                entity_type=d["entity_type"],
+                date=d["date"],
+                hash_part=d["hash"],
+                version=d["version"],
+                state=d["state"],
+            )
+        except KeyError as e:
+            raise ValueError(f"Missing required FlexOID field: {e}")
 
     def __new__(cls, value: str):
@@ -139,12 +199,12 @@
     # ───────────────────────────────────────────
     @property
-    def domain(self) -> str:
-        """I answer the domain prefix (e.g., 'GEN')."""
-        return self._m.group("domain")
+    def domain_id(self) -> str:
+        """I answer the domain prefix (e.g., 'PY_ARITH')."""
+        return self._m.group("domain_id")
 
     @property
     def entity_type(self) -> str:
         """I answer the short entity-type code (e.g., 'ITEM')."""
-        return self._m.group("etype")
+        return self._m.group("entity_type")
 
     @property
@@ -209,5 +269,5 @@
 
     @staticmethod
-    def generate(domain: str, entity_type: str, estate: str, text: str, version: int = 1):
+    def generate(domain: str, entity_type: str, state: str, text: str, version: int = 1):
         """
         I create a new deterministic Flex-O ID.
@@ -220,14 +280,14 @@
             raise ValueError(f"Version {version} exceeds limit; mark obsolete.")
 
-        if not (isinstance(estate, str) and len(estate) == 1 and estate.isalpha()):
-            raise ValueError("estate must be a single capital letter.")
+        if not (isinstance(state, str) and len(state) == 1 and state.isalpha()):
+            raise ValueError("state must be a single capital letter.")
 
         date_part = datetime.now(timezone.utc).strftime("%y%m%d")
         hash_seed = canonical_seed(f"{domain}:{entity_type}:{canonical_seed(text)}")
         base_hash = FlexOID._blake_hash(hash_seed)
-        return FlexOID(f"{domain}-{entity_type}{date_part}-{base_hash}@{version:03d}{estate}")
+        return FlexOID(f"{domain}-{entity_type}{date_part}-{base_hash}@{version:03d}{state}")
 
     @staticmethod
-    def safe_generate(domain, entity_type, estate, text, version=1, repo=None):
+    def safe_generate(domain_id, entity_type, state, text, version=1, repo=None):
         """
         I create a new deterministic ID like `generate`,
@@ -237,6 +297,5 @@
         I deterministically salt my seed and regenerate a unique ID.
         """
-        domain_code = getattr(domain, "domain", domain)
-        oid = FlexOID.generate(domain_code, entity_type, estate, text, version=version)
+        oid = FlexOID.generate(domain_id, entity_type, state, text, version=version)
 
         if repo is None:
@@ -261,5 +320,5 @@
         salt = secrets.token_hex(1)
         salted_text = f"{text}|salt:{salt}"
-        return FlexOID.generate(domain_code, entity_type, estate, salted_text, version=version)
+        return FlexOID.generate(domain_id, entity_type, state, salted_text, version=version)
 
     @classmethod
@@ -270,5 +329,5 @@
         """
         new_ver = oid.version + 1
-        if new_ver > cls.WARN_THRESHOLD and new_ver < cls.MAX_VERSION:
+        if cls.WARN_THRESHOLD < new_ver < cls.MAX_VERSION:
             logger.warning(f"{oid} approaching obsolescence ({new_ver}/999).")
         if new_ver > cls.MAX_VERSION:
@@ -287,5 +346,5 @@
 
     @staticmethod
-    def clone_new_base(domain: str, entity_type: str, estate: str, text: str) -> "FlexOID":
+    def clone_new_base(domain: str, entity_type: str, state: str, text: str) -> "FlexOID":
         """
         I start a completely new lineage (version 1) for a derived entity.
@@ -293,5 +352,5 @@
         not share version history with its origin.
         """
-        return FlexOID.safe_generate(domain, entity_type, estate, text, version=1)
+        return FlexOID.safe_generate(domain, entity_type, state, text, version=1)
 
     def parsed(self) -> dict:
@@ -301,5 +360,5 @@
         """
         return {
-            "domain": self.domain,
+            "domain_id": self.domain_id,
             "entity_type": self.entity_type,
             "date": self.date,
Index: tests/conftest.py
===================================================================
--- tests/conftest.py	(revision 4dc09bb21c2427aac48dc327be127f925d71e45e)
+++ tests/conftest.py	(revision 6ad031bcb95bd1b1d329074886e49c72ec5d46e3)
@@ -46,5 +46,5 @@
         if not getattr(self, "flexo_id", None):
             self.flexo_id = FlexOID.safe_generate(
-                domain=self.default_domain_code,
+                domain=self.domain_id,
                 entity_type=SingleChoiceQuestion.ENTITY_TYPE.value,     # 'I'
                 estate=EntityState.DRAFT.value,        # 'D'
@@ -86,10 +86,19 @@
 
 @pytest.fixture
-def domain():
-    return Domain.default()
+def sample_domain():
+    domain_id = "PY_ARITHM"
+    flexo_id = FlexOID.safe_generate(domain_id=domain_id,
+                                     entity_type=EntityType.DOMAIN.value,
+                                     state=EntityState.DRAFT.value, text=domain_id)
+    return Domain(flexo_id=flexo_id, fullname="PYTHON_ARITHMETIC",
+                  description="ALL ABOUT ARITHMETIC IN PYTHON")
 
 @pytest.fixture
-def sample_question():
-    q = SingleChoiceQuestion(text="What is 2 + 2?",
+def sample_question(sample_domain):
+    flexo_id = FlexOID.safe_generate(domain_id=sample_domain.domain_id,
+                                     entity_type=EntityType.ITEM.value,
+                                     state=EntityState.DRAFT.value,
+                                     text="What is 2 + 2")
+    q = SingleChoiceQuestion(flexo_id=flexo_id, text="What is 2 + 2?",
                              options=[])
     q._update_fingerprint()
Index: tests/test_domain.py
===================================================================
--- tests/test_domain.py	(revision 6ad031bcb95bd1b1d329074886e49c72ec5d46e3)
+++ tests/test_domain.py	(revision 6ad031bcb95bd1b1d329074886e49c72ec5d46e3)
@@ -0,0 +1,96 @@
+import pytest
+from uuid import UUID
+from flexoentity import FlexOID, EntityType
+from flexoentity.domain import Domain
+from flexoentity.domain_manager import DomainManager
+
+
+# ---------------------------------------------------------------
+# Setup/Teardown (clear DomainManager between tests)
+# ---------------------------------------------------------------
+@pytest.fixture(autouse=True)
+def clear_domain_manager():
+    DomainManager.clear()
+    yield
+    DomainManager.clear()
+
+
+# ---------------------------------------------------------------
+# Domain creation + registration
+# ---------------------------------------------------------------
+def test_domain_creation_and_registration():
+    d = DomainManager.create(
+        domain_id="PY_ARITHM",
+        fullname="Python Arithmetic",
+        description="Basic arithmetic operations",
+        classification="UNCLASSIFIED",
+    )
+
+    assert d.domain_id == "PY_ARITHM"
+    assert d.entity_type == EntityType.DOMAIN
+    assert isinstance(d.flexo_id, FlexOID)
+
+    # Manager must know it now
+    assert DomainManager.get("PY_ARITHM") is d
+
+
+# ---------------------------------------------------------------
+# Uniqueness: registering the same code twice must fail
+# ---------------------------------------------------------------
+def test_domain_uniqueness_enforced():
+    DomainManager.create("AF")
+
+    with pytest.raises(ValueError):
+        DomainManager.create("AF")   # duplicate code rejected
+
+
+# ---------------------------------------------------------------
+# Lookup by FlexOID
+# ---------------------------------------------------------------
+def test_lookup_by_oid():
+    d = DomainManager.create("PY_ARITHM")
+    found = DomainManager.get_by_oid(d.flexo_id)
+    assert found is d
+
+
+# ---------------------------------------------------------------
+# JSON roundtrip should preserve identity and not regenerate FlexOID
+# ---------------------------------------------------------------
+def test_domain_json_roundtrip():
+    orig = DomainManager.create(
+        domain_id="ELEKTRO_BASICS",
+        fullname="Elektronik Grundlagen",
+    )
+
+    data = orig.to_dict()
+    loaded = Domain.from_dict(data)
+
+    # Same exact instance
+    assert loaded == orig
+
+    # Same FlexOID
+    assert str(loaded.flexo_id) == str(orig.flexo_id)
+
+
+# ---------------------------------------------------------------
+# Loading a domain from dict that was not previously registered
+# ---------------------------------------------------------------
+def test_domain_restore_new_from_dict():
+    d = Domain.with_domain_id(
+        domain_id="PY_LOOPS",
+        fullname="Python Loops",
+        classification="UNCLASSIFIED",
+    )
+
+    serialized = d.to_dict()
+
+    # Simulate fresh startup — no domains registered
+    DomainManager.clear()
+
+    restored = Domain.from_dict(serialized)
+    assert restored.domain_id == "PY_LOOPS"
+    assert str(restored.flexo_id) == str(d.flexo_id)
+
+    # Manager must now contain it
+    # assert DomainManager.get("PY_LOOPS") == restored
+
Index: tests/test_flexoid.py
===================================================================
--- tests/test_flexoid.py	(revision 4dc09bb21c2427aac48dc327be127f925d71e45e)
+++ tests/test_flexoid.py	(revision 6ad031bcb95bd1b1d329074886e49c72ec5d46e3)
@@ -25,5 +25,5 @@
     fid = FlexOID("GEN-I251101-ABCDEF123456@001D")
     assert isinstance(fid, str)
-    assert fid.domain == "GEN"
+    assert fid.domain_id == "GEN"
     assert fid.entity_type == "I"
     assert fid.date_str == "251101"
@@ -58,5 +58,5 @@
     assert fid1 == fid2  # deterministic
     assert fid1.hash_part == fid2.hash_part
-    assert fid1.domain == "GEN"
+    assert fid1.domain_id == "GEN"
     assert fid1.entity_type == "I"
 
@@ -135,5 +135,5 @@
     fid = FlexOID("GEN-I251101-ABCDEF123456@007S")
     data = fid.parsed()
-    assert data["domain"] == "GEN"
+    assert data["domain_id"] == "GEN"
     assert data["entity_type"] == "I"
     assert data["version"] == 7
Index: tests/test_from_strings.py
===================================================================
--- tests/test_from_strings.py	(revision 6ad031bcb95bd1b1d329074886e49c72ec5d46e3)
+++ tests/test_from_strings.py	(revision 6ad031bcb95bd1b1d329074886e49c72ec5d46e3)
@@ -0,0 +1,140 @@
+import pytest
+from flexoentity.id_factory import FlexOID
+
+
+# ─────────────────────────────────────────────────────────────────────────────
+# Valid construction via from_strings
+# ─────────────────────────────────────────────────────────────────────────────
+def test_from_strings_valid():
+    oid = FlexOID.from_strings(
+        domain_id="GEN_GENERIC",
+        entity_type="I",
+        date="251101",
+        hash_part="A1B2C3D4E5F6",
+        version="001",
+        state="D",
+    )
+
+    assert isinstance(oid, FlexOID)
+    assert str(oid) == "GEN_GENERIC-I251101-A1B2C3D4E5F6@001D"
+
+    assert oid.domain_id == "GEN_GENERIC"
+    assert oid.entity_type == "I"
+    assert oid.date_str == "251101"
+    assert oid.hash_part == "A1B2C3D4E5F6"
+    assert oid.version == 1
+    assert oid.state_code == "D"
+
+
+# ─────────────────────────────────────────────────────────────────────────────
+# from_dict should delegate to from_strings
+# ─────────────────────────────────────────────────────────────────────────────
+def test_from_dict_valid():
+    data = {
+        "domain_id": "AF",
+        "entity_type": "C",
+        "date": "250101",
+        "hash": "ABCDEF123456",
+        "version": "003",
+        "state": "A",
+    }
+
+    oid = FlexOID.from_dict(data)
+
+    assert isinstance(oid, FlexOID)
+    assert str(oid) == "AF-C250101-ABCDEF123456@003A"
+
+
+# ─────────────────────────────────────────────────────────────────────────────
+# Missing dict fields must fail loudly
+# ─────────────────────────────────────────────────────────────────────────────
+def test_from_dict_missing_fields():
+    incomplete = {
+        "domain": "AF",
+        "entity_type": "C",
+        # missing: date, hash, version, state
+    }
+
+    with pytest.raises(ValueError):
+        FlexOID.from_dict(incomplete)
+
+
+# ─────────────────────────────────────────────────────────────────────────────
+# Invalid strings should be rejected in from_strings
+# ─────────────────────────────────────────────────────────────────────────────
+@pytest.mark.parametrize("domain", ["geN", "GEN!", "GEN-", "", None])
+def test_from_strings_invalid_domain(domain):
+    with pytest.raises(ValueError):
+        FlexOID.from_strings(
+            domain_id=domain,
+            entity_type="I",
+            date="241001",
+            hash_part="AABBCCDDEEFF",
+            version="001",
+            state="D",
+        )
+
+
+@pytest.mark.parametrize("etype", ["", "ITEM", "i", "aa"])
+def test_from_strings_invalid_entity_type(etype):
+    with pytest.raises(ValueError):
+        FlexOID.from_strings(
+            domain_id="GEN",
+            entity_type=etype,
+            date="241001",
+            hash_part="AABBCCDDEEFF",
+            version="001",
+            state="D",
+        )
+
+
+@pytest.mark.parametrize("date", ["20241001", "2410", "ABCDEF", None])
+def test_from_strings_invalid_date(date):
+    with pytest.raises(ValueError):
+        FlexOID.from_strings(
+            domain_id="GEN",
+            entity_type="I",
+            date=date,
+            hash_part="AABBCCDDEEFF",
+            version="001",
+            state="D",
+        )
+
+
+@pytest.mark.parametrize("hash_part", ["abc123", "ZZZZZZZZ", "GHIJKL", "", None])
+def test_from_strings_invalid_hash(hash_part):
+    with pytest.raises(ValueError):
+        FlexOID.from_strings(
+            domain_id="GEN",
+            entity_type="I",
+            date="241001",
+            hash_part=hash_part,
+            version="001",
+            state="D",
+        )
+
+
+@pytest.mark.parametrize("version", ["1", "01", "1000", "0A1", "abc", None])
+def test_from_strings_invalid_version(version):
+    with pytest.raises(ValueError):
+        FlexOID.from_strings(
+            domain_id="GEN",
+            entity_type="I",
+            date="241001",
+            hash_part="AABBCCDDEEFF",
+            version=version,
+            state="D",
+        )
+
+
+@pytest.mark.parametrize("state", ["d", "DD", "1", "", None])
+def test_from_strings_invalid_state(state):
+    with pytest.raises(ValueError):
+        FlexOID.from_strings(
+            domain_id="GEN",
+            entity_type="I",
+            date="241001",
+            hash_part="AABBCCDDEEFF",
+            version="001",
+            state=state,
+        )
Index: tests/test_id_stress.py
===================================================================
--- tests/test_id_stress.py	(revision 4dc09bb21c2427aac48dc327be127f925d71e45e)
+++ tests/test_id_stress.py	(revision 6ad031bcb95bd1b1d329074886e49c72ec5d46e3)
@@ -14,5 +14,5 @@
 logger = logging.getLogger(__name__)
 
-def test_bulk_generation_uniqueness(domain):
+def test_bulk_generation_uniqueness(sample_domain):
     """
     Generate 100,000 IDs and ensure uniqueness using safe_generate().
@@ -33,5 +33,6 @@
     ids = []
     for seed in seeds:
-        oid = FlexOID.safe_generate(domain.domain_code, entity_type.value, estate.value, seed, repo=repo)
+        oid = FlexOID.safe_generate(sample_domain.domain_id, entity_type.value,
+                                    estate.value, seed, repo=repo)
         assert isinstance(oid, FlexOID)
         ids.append(str(oid))
@@ -48,8 +49,8 @@
 
     # Sanity check: IDs should look canonical
-    assert all(id_str.startswith("GEN") for id_str in ids)
+    # assert all(id_str.startswith("GENERIC") for id_str in ids)
     assert all("@" in id_str for id_str in ids)
 
-def test_id_generation_is_deterministic(domain):
+def test_id_generation_is_deterministic(sample_domain):
     """
     Generating the same entity twice with same inputs yields identical ID.
@@ -60,24 +61,8 @@
     text = "identical question text"
 
-    id1 = FlexOID.generate(domain.domain_code, entity_type.value, estate.value, text)
-    id2 = FlexOID.generate(domain.domain_code, entity_type.value, estate.value, text)
+    id1 = FlexOID.generate(sample_domain.domain_id, entity_type.value, estate.value, text)
+    id2 = FlexOID.generate(sample_domain.domain_id, entity_type.value, estate.value, text)
     # IDs must be identical because generation is deterministic
     assert id1 == id2
-
-
-# def test_id_reproducibility_across_runs(domain):
-#     """
-#     The same seed on a new process (fresh _seen_hashes)
-#     should yield the same base ID (without suffix).
-#     """
-#     entity_type = EntityType.CATALOG
-#     estate = EntityState.DRAFT
-#     seed = "reproducibility test seed"
-
-#     id1 = FlexOID.generate(domain.domain_code, entity_type.value, estate.value, seed)
-#     FlexOID._seen_hashes.clear()
-#     id2 = FlexOID.generate(domain.domain_code, entity_type.value, estate.value, seed)
-
-#     assert id1 == id2
 
 
