source: flexoentity/org/README.org@ d7499ca

Last change on this file since d7499ca was d7499ca, checked in by Enrico Schwass <ennoausberlin@…>, 7 weeks ago

Signature support for Linux and MacOS added

  • Property mode set to 100644
File size: 11.9 KB
Line 
1#+TITLE: flexoentity
2#+SUBTITLE: A hardened entity base and deterministic identifier system for the Flex-O family
3#+AUTHOR: Enno
4#+DATE: 2025-10-20
5#+OPTIONS: toc:3 num:nil
6
7* Overview
8
9`flexoentity` provides the *identity and lifecycle backbone* for all Flex-O components
10(Flex-O-Grader, Flex-O-Vault, Flex-O-Drill, …).
11
12It defines how entities such as questions, media, catalogs, and exams are *identified, versioned, signed, and verified* — all without any external database dependencies.
13
14At its heart lie two modules:
15
16- =id_factory.py= – robust, cryptographically-verifiable *Flex-O ID generator*
17- =flexo_entity.py= – abstract *base class for all versioned entities*
18
19Together, they form a compact yet powerful foundation for audit-ready, reproducible data structures across offline and air-gapped deployments.
20
21- Design Goals
22
23|----------------|--------------------------------------------------------------------------------------------------------|
24| Goal | Description |
25|----------------|--------------------------------------------------------------------------------------------------------|
26| *Determinism* | IDs are derived from canonicalized entity content — identical input always yields identical ID prefix. |
27| *Integrity* | BLAKE2s hashing and digital signatures protect against manual tampering. |
28| *Traceability* | Version numbers (=@001A=, =@002S=, …) track entity lifecycle transitions. |
29| *Stability* | Hash prefixes remain constant across state changes; only version and state suffixes evolve. |
30| *Auditability* | Every entity can be serialized, verified, and reconstructed without hidden dependencies. |
31| *Simplicity* | Pure-Python, zero external libraries, self-contained and easy to embed. |
32|----------------|--------------------------------------------------------------------------------------------------------|
33
34* Flex-O ID Structure
35
36Each entity carries a unique *Flex-O ID*, generated by =FlexOID.generate()=.
37
38#+BEGIN_EXAMPLE
39AF-I250101-9A4C2D@003S
40#+END_EXAMPLE
41
42|-----------+-----------------+----------------------------------------------|
43| Segment | Example | Meaning |
44|-----------+-----------------+----------------------------------------------|
45| *Domain* | =AF or PY_LANG= | Uppercase - Logical scope (e.g. "Air Force") |
46| *Type* | =I= | Entity type (e.g. ITEM) |
47| *Date* | =250101= | UTC creation date (YYMMDD) |
48| *Hash* | =9A4C2D4F6E53= | 12-digit BLAKE2s digest of canonical content |
49| *Version* | =003= | Sequential version counter |
50| *State* | =S= | Lifecycle state (D, A, S, P, O) |
51|-----------+-----------------+----------------------------------------------|
52
53
54* Lifecycle States
55
56|-----------------------|---------|-----------------------------|
57| State | Abbrev. | Description |
58|-----------------------|---------|-----------------------------|
59| *DRAFT* | =D= | Editable, not yet validated |
60| *APPROVED* | =A= | Reviewed and accepted |
61| *APPROVED_AND_SIGNED* | =S= | Cryptographically signed |
62| *PUBLISHED* | =P= | Released to consumers |
63| *OBSOLETE* | =O= | Archived or replaced |
64|-----------------------|---------|-----------------------------|
65
66Transitions follow a strict progression:
67#+BEGIN_EXAMPLE
68DRAFT -> APPROVED -> APPROVED_AND_SIGNED -> PUBLISHED -> OBSOLETE
69#+END_EXAMPLE
70
71Only DRAFT entities can be deleted - all others got OBSOLETE mark instead
72
73* Core Classes
74
75** FlexOID
76
77A lightweight immutable class representing the full identity of an entity.
78
79*Highlights*
80- safe_generate(domain, entity_type, estate, text, version=1, repo) -> create a new ID
81- next_version(oid) -> increment version safely
82- clone_new_base(domain, entity_type, estate, text) -> start a new lineage
83- Deterministic prefix, state-dependent signature
84
85** =FlexoEntity=
86Abstract base class for all versioned entities (e.g., Question, Exam, Catalog).
87
88Implements:
89- ID lifecycle management (approve(), sign(), publish(), obsolete())
90- Serialization (to_json(), from_json(), to_dict(), from_dict())
91- Integrity verification (verify_integrity(entity))
92- Controlled state transitions with automatic timestamps
93
94Subclasses define a single property:
95
96#+BEGIN_SRC python
97@property
98def text_seed(self) -> str:
99 """Canonical text or core content for hashing."""
100#+END_SRC
101
102* Integrity Verification
103
104Each entity can self-verify its integrity:
105
106#+BEGIN_SRC python
107entity = Question.with_domain_id(domain_id="AF", text="What is Ohm’s law?", topic="Electronics")
108json_str = entity.to_json()
109reloaded = Question.from_json(json_str)
110
111assert FlexoEntity.verify_integrity(reloaded)
112#+END_SRC
113
114If the file is tampered with (e.g. "Ohm’s" → "Omm’s"), verification fails:
115
116* Real World Example
117
118Below you can see the implementation of a dedicated FlexoEntity class, used for Domains.
119We set an ENTITY_TYPE and define the needed fields in the data class. We define how to create
120a default object, the text_seed (it is easy because the domain id is unique and therefore sufficient)
121and the methods for serialization.
122
123#+BEGIN_SRC python
124from uuid import UUID
125from dataclasses import dataclass
126from flexoentity import FlexOID, FlexoEntity, EntityType
127
128@dataclass
129class Domain(FlexoEntity):
130 """
131 I am a helper class to provide more information than just a
132 domain abbreviation in FlexOID, doing mapping and management
133 """
134
135 ENTITY_TYPE = EntityType.DOMAIN
136
137 fullname: str = ""
138 description: str = ""
139 classification: str = "UNCLASSIFIED"
140
141 @classmethod
142 def default(cls):
143 """Return the default domain object."""
144 return cls.with_domain_id(domain_id="GEN_GENERIC",
145 fullname="Generic Domain", classification="UNCLASSIFIED")
146
147 @property
148 def text_seed(self) -> str:
149 return self.domain_id
150
151 def to_dict(self):
152 base = super().to_dict()
153 base.update({
154 "flexo_id": self.flexo_id,
155 "domain_id": self.domain_id,
156 "fullname": self.fullname,
157 "description": self.description,
158 "classification": self.classification,
159 })
160 return base
161
162 @classmethod
163 def from_dict(cls, data):
164 # Must have flexo_id
165 if "flexo_id" not in data:
166 raise ValueError("Domain serialization missing 'flexo_id'.")
167
168 flexo_id = FlexOID(data["flexo_id"])
169
170 obj = cls(
171 fullname=data.get("fullname", ""),
172 description=data.get("description", ""),
173 classification=data.get("classification", "UNCLASSIFIED"),
174 flexo_id=flexo_id,
175 _in_factory=True
176 )
177
178 # Restore metadata
179 obj.origin = data.get("origin")
180 obj.fingerprint = data.get("fingerprint", "")
181 obj.originator_id = (
182 UUID(data["originator_id"]) if data.get("originator_id") else None
183 )
184 obj.owner_id = (
185 UUID(data["owner_id"]) if data.get("owner_id") else None
186 )
187
188 return obj
189#+END_SRC
190
191* Usage
192#+BEGIN_SRC python
193d = Domain.default()
194print(d.flexo_id) # GEN_GENERIC-D251124-67C2CAE292CE@001D
195d.approve()
196print(d.flexo_id) # GEN_GENERIC-D251124-67C2CAE292CE@001A
197d.sign()
198print(d.flexo_id) # GEN_GENERIC-D251124-67C2CAE292CE@001S
199#+END_SRC
200
201* Serialization Example
202
203#+BEGIN_SRC python
204{
205 'flexo_id': FlexOID(GEN_GENERIC-D251124-29CE0F4BE59D@001S),
206 'fingerprint': '534BD2EC5C5511F1',
207 'origin': FlexOID(GEN_GENERIC-D251124-67C2CAE292CE@001D),
208 'originator_id': '00000000-0000-0000-0000-000000000000',
209 'owner_id': '00000000-0000-0000-0000-000000000000',
210 'domain_id': 'GEN_GENERIC',
211 'fullname': 'Generic Domain',
212 'description': '',
213 'classification': 'UNCLASSIFIED'}
214#+END_SRC
215
216#+BEGIN_SRC js
217
218{
219 "flexo_id": "GEN_GENERIC-D251124-29CE0F4BE59D@001S",
220 "fingerprint": "534BD2EC5C5511F1",
221 "origin": "GEN_GENERIC-D251124-67C2CAE292CE@001D",
222 "originator_id": "00000000-0000-0000-0000-000000000000",
223 "owner_id": "00000000-0000-0000-0000-000000000000",
224 "domain_id": "GEN_GENERIC",
225 "fullname": "Generic Domain",
226 "description": "",
227 "classification": "UNCLASSIFIED"
228}
229
230
231
232* Entity Type and State Codes
233
234|-------------+------+----------------------------------------------------------------------------|
235| EntityType | Code | Typical Use |
236|-------------+------+----------------------------------------------------------------------------|
237| GENERIC | G | Generic entities that does not fit other types yet or are temporarily only |
238| DOMAIN | D | Every Domain is of this type |
239| MEDIA | M | Every media item belongs to this type, e.g. Pictures, Audio, Video |
240| ITEM | I | An Entity what is usually used in a collection, e.g. Questions in a test |
241| COLLECTION | C | A collection of items, as an Exam or a catalog |
242| TEXT | T | A text document |
243| HANDOUT | H | A published document |
244| OUTPUT | O | The output of a computation |
245| RECORD | R | Record type data, as bibliography entries |
246| SESSION | S | A unique session, e.g. managed by a session manager
247| USER | U | User objects |
248| CONFIG | F | CONFIG files that need to be tracked over time and state |
249| EVENT | E | Events that have to be tracked over time, as status messages or orders |
250| ATTESTATION | X | Entities that attest a formal technical (not human) check e.g. Signatures |
251|-------------+------+----------------------------------------------------------------------------|
252
253|---------------------|------|-------------------|
254| EntityState | Code | Meaning |
255|---------------------|------|-------------------|
256| DRAFT | D | Work in progress |
257| APPROVED | A | Reviewed |
258| APPROVED_AND_SIGNED | S | Signed version |
259| PUBLISHED | P | Publicly released |
260| OBSOLETE | O | Deprecated |
261|---------------------|------|-------------------|
262
263* Design Notes
264- *Hash Stability:* Only domain, entity type, and content text influence the hash.
265 This ensures consistent prefixes across state changes.
266- *State-Dependent Signatures:* Each lifecycle stage has its own signature seed.
267 Modifying a file without re-signing invalidates integrity.
268- *Obsolescence Threshold:* Version numbers above 900 trigger warnings;
269 beyond 999 are considered obsolete.
270- *Clone Lineages:* Cloning an entity resets versioning but preserves metadata lineage.
271
272* Dependencies
273- Python 3.11+
274- Standard library only (=hashlib=, =json=, =datetime=, =enum=, =dataclasses=)
275
276No external packages. Fully compatible with *Guix*, *air-gapped* deployments, and *reproducible builds*.
277
278* Integration
279`flexoentity` is imported by higher-level modules such as:
280
281- *Flex-O-Grader* → manages question catalogs and exam bundles
282- *Flex-O-Vault* → provides persistent media storage with metadata integrity
283- *Flex-O-Drill* → uses versioned entities for training simulations
284
285All share the same identity and versioning logic — ensuring that
286*what was approved, signed, and published remains provably authentic.*
287
288* License
289MIT License 2025
290Part of the *Flex-O family* by Flex-O-Dyne GmbH
291Designed for reproducible, audit-ready, human-centered software.
Note: See TracBrowser for help on using the repository browser.