wiki:WikiStart

Version 4 (modified by enno, 5 days ago) ( diff )

--

Overview

flexoentity provides the identity and lifecycle backbone for all Flex-O components (Flex-O-Grader, Flex-O-Vault, Flex-O-Drill).

It defines how entities such as questions, media, catalogs, and exams are *identified, versioned, signed, and verified* — all without any external database dependencies.

At its heart lie two modules:

  • id_factory.py – robust, cryptographically-verifiable Flex-O ID generator
  • flexo_entity.py – abstract base class for all versioned entities

Together, they form a compact yet powerful foundation for audit-ready, reproducible data structures across offline and air-gapped deployments.

  • Design Goals
|

| Goal | Description |

|

| *Determinism* | IDs are derived from canonicalized entity content — identical input always yields identical ID prefix. | | *Integrity* | BLAKE2s hashing and digital signatures protect against manual tampering. | | *Traceability* | Version numbers (=@001A=, =@002S=, …) track entity lifecycle transitions. | | *Stability* | Hash prefixes remain constant across state changes; only version and state suffixes evolve. | | *Auditability* | Every entity can be serialized, verified, and reconstructed without hidden dependencies. | | *Simplicity* | Pure-Python, zero external libraries, self-contained and easy to embed. |

|
  • Flex-O ID Structure

Each entity carries a unique *Flex-O ID*, generated by =FlexOID.generate()=.

#+BEGIN_EXAMPLE AF-I250101-9A4C2D@003S #+END_EXAMPLE

+-----------------+----------------------------------------------|

| Segment | Example | Meaning |

+-----------------+----------------------------------------------|

| *Domain* | =AF or PY_LANG= | Uppercase - Logical scope (e.g. "Air Force") | | *Type* | =I= | Entity type (e.g. ITEM) | | *Date* | =250101= | UTC creation date (YYMMDD) | | *Hash* | =9A4C2D4F6E53= | 12-digit BLAKE2s digest of canonical content | | *Version* | =003= | Sequential version counter | | *State* | =S= | Lifecycle state (D, A, S, P, O) |

+-----------------+----------------------------------------------|
  • Lifecycle States
|

| State | Abbrev. | Description |

|

| *DRAFT* | =D= | Editable, not yet validated | | *APPROVED* | =A= | Reviewed and accepted | | *APPROVED_AND_SIGNED* | =S= | Cryptographically signed | | *PUBLISHED* | =P= | Released to consumers | | *OBSOLETE* | =O= | Archived or replaced |

|

Transitions follow a strict progression: #+BEGIN_EXAMPLE DRAFT -> APPROVED -> APPROVED_AND_SIGNED -> PUBLISHED -> OBSOLETE #+END_EXAMPLE

Only DRAFT entities can be deleted - all others got OBSOLETE mark instead

  • Core Classes

FlexOID

A lightweight immutable class representing the full identity of an entity.

*Highlights*

  • safe_generate(domain, entity_type, estate, text, version=1, repo) -> create a new ID
  • next_version(oid) -> increment version safely
  • clone_new_base(domain, entity_type, estate, text) -> start a new lineage
  • Deterministic prefix, state-dependent signature

=FlexoEntity= Abstract base class for all versioned entities (e.g., Question, Exam, Catalog).

Implements:

  • ID lifecycle management (approve(), sign(), publish(), obsolete())
  • Serialization (to_json(), from_json(), to_dict(), from_dict())
  • Integrity verification (verify_integrity(entity))
  • Controlled state transitions with automatic timestamps

Subclasses define a single property:

#+BEGIN_SRC python @property def text_seed(self) -> str:

"""Canonical text or core content for hashing."""

#+END_SRC

  • Integrity Verification

Each entity can self-verify its integrity:

#+BEGIN_SRC python entity = Question.with_domain_id(domain_id="AF", text="What is Ohm’s law?", topic="Electronics") json_str = entity.to_json() reloaded = Question.from_json(json_str)

assert FlexoEntity.verify_integrity(reloaded) #+END_SRC

If the file is tampered with (e.g. "Ohm’s" → "Omm’s"), verification fails:

  • Real World Example

Below you can see the implementation of a dedicated FlexoEntity class, used for Domains. We set an ENTITY_TYPE and define the needed fields in the data class. We define how to create a default object, the text_seed (it is easy because the domain id is unique and therefore sufficient) and the methods for serialization.

#+BEGIN_SRC python from uuid import UUID from dataclasses import dataclass from flexoentity import FlexOID, FlexoEntity, EntityType

@dataclass class Domain(FlexoEntity):

""" I am a helper class to provide more information than just a domain abbreviation in FlexOID, doing mapping and management """

ENTITY_TYPE = EntityType.DOMAIN

fullname: str = "" description: str = "" classification: str = "UNCLASSIFIED"

@classmethod def default(cls):

"""Return the default domain object.""" return cls.with_domain_id(domain_id="GEN_GENERIC",

fullname="Generic Domain", classification="UNCLASSIFIED")

@property def text_seed(self) -> str:

return self.domain_id

def to_dict(self):

base = super().to_dict() base.update({

"flexo_id": self.flexo_id, "domain_id": self.domain_id, "fullname": self.fullname, "description": self.description, "classification": self.classification,

}) return base

@classmethod def from_dict(cls, data):

# Must have flexo_id if "flexo_id" not in data:

raise ValueError("Domain serialization missing 'flexo_id'.")

flexo_id = FlexOID(dataflexo_id)

obj = cls(

fullname=data.get("fullname", ""), description=data.get("description", ""), classification=data.get("classification", "UNCLASSIFIED"), flexo_id=flexo_id, _in_factory=True

)

# Restore metadata obj.origin = data.get("origin") obj.fingerprint = data.get("fingerprint", "") obj.originator_id = (

UUID(dataoriginator_id) if data.get("originator_id") else None

) obj.owner_id = (

UUID(dataowner_id) if data.get("owner_id") else None

)

return obj

#+END_SRC

  • Usage

#+BEGIN_SRC python d = Domain.default() print(d.flexo_id) # GEN_GENERIC-D251124-67C2CAE292CE@001D d.approve() print(d.flexo_id) # GEN_GENERIC-D251124-67C2CAE292CE@001A d.sign() print(d.flexo_id) # GEN_GENERIC-D251124-67C2CAE292CE@001S #+END_SRC

  • Serialization Example

to_dict()

#+BEGIN_SRC python {

'flexo_id': FlexOID(GEN_GENERIC-D251124-29CE0F4BE59D@001S), 'fingerprint': '534BD2EC5C5511F1', 'origin': FlexOID(GEN_GENERIC-D251124-67C2CAE292CE@001D), 'originator_id': '00000000-0000-0000-0000-000000000000', 'owner_id': '00000000-0000-0000-0000-000000000000', 'domain_id': 'GEN_GENERIC', 'fullname': 'Generic Domain', 'description': , 'classification': 'UNCLASSIFIED'}

#+END_SRC

to_json()

#+BEGIN_SRC js {

"flexo_id": "GEN_GENERIC-D251124-29CE0F4BE59D@001S", "fingerprint": "534BD2EC5C5511F1", "origin": "GEN_GENERIC-D251124-67C2CAE292CE@001D", "originator_id": "00000000-0000-0000-0000-000000000000", "owner_id": "00000000-0000-0000-0000-000000000000", "domain_id": "GEN_GENERIC", "fullname": "Generic Domain", "description": "", "classification": "UNCLASSIFIED"

} #+END_SRC

  • Entity Type and State Codes
+------+----------------------------------------------------------------------------|

| EntityType | Code | Typical Use |

+------+----------------------------------------------------------------------------|

| GENERIC | G | Generic entities that does not fit other types yet or are temporarily only | | DOMAIN | D | Every Domain is of this type | | MEDIA | M | Every media item belongs to this type, e.g. Pictures, Audio, Video | | ITEM | I | An Entity what is usually used in a collection, e.g. Questions in a test | | COLLECTION | C | A collection of items, as an Exam or a catalog | | TEXT | T | A text document | | HANDOUT | H | A published document | | OUTPUT | O | The output of a computation | | RECORD | R | Record type data, as bibliography entries | | SESSION | S | A unique session, e.g. managed by a session manager | USER | U | User objects | | CONFIG | F | CONFIG files that need to be tracked over time and state | | EVENT | E | Events that have to be tracked over time, as status messages or orders | | ATTESTATION | X | Entities that attest a formal technical (not human) check e.g. Signatures |

+------+----------------------------------------------------------------------------|
|

| EntityState | Code | Meaning |

|

| DRAFT | D | Work in progress | | APPROVED | A | Reviewed | | APPROVED_AND_SIGNED | S | Signed version | | PUBLISHED | P | Publicly released | | OBSOLETE | O | Deprecated |

|
  • Design Notes
  • *Hash Stability:* Only domain, entity type, and content text influence the hash. This ensures consistent prefixes across state changes.
  • *State-Dependent Signatures:* Each lifecycle stage has its own signature seed. Modifying a file without re-signing invalidates integrity.
  • *Obsolescence Threshold:* Version numbers above 900 trigger warnings; beyond 999 are considered obsolete.
  • *Clone Lineages:* Cloning an entity resets versioning but preserves metadata lineage.
  • Dependencies
  • Python 3.11+
  • Standard library only (=hashlib=, =json=, =datetime=, =enum=, =dataclasses=)

No external packages. Fully compatible with *Guix*, *air-gapped* deployments, and *reproducible builds*.

  • Integration

flexoentity is imported by higher-level modules such as:

  • *Flex-O-Grader* → manages question catalogs and exam bundles
  • *Flex-O-Vault* → provides persistent media storage with metadata integrity
  • *Flex-O-Drill* → uses versioned entities for training simulations

All share the same identity and versioning logic — ensuring that *what was approved, signed, and published remains provably authentic.*

  • License

MIT License 2025 Part of the *Flex-O family* by Flex-O-Dyne GmbH Designed for reproducible, audit-ready, human-centered software.

Note: See TracWiki for help on using the wiki.