source: flexoentity/README.md@ a8dca95

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

typo fixed

  • Property mode set to 100644
File size: 15.4 KB
Line 
1
2# Table of Contents
3
4- [Overview](#org77ab3e0)
5- [Flex-O ID Structure](#orgf136db1)
6- [Lifecycle States](#orgea1c2ca)
7- [Core Classes](#org2b0320a)
8 - [FlexOID](#org39ec589)
9 - [`FlexoEntity`](#orgd84c86d)
10- [Integrity Verification](#org4c3e14b)
11- [Real World Example](#orgfb82c02)
12- [Usage](#orge03e624)
13- [Serialization Example](#org118d77d)
14- [Entity Type and State Codes](#org83e21be)
15- [Design Notes](#orga31954b)
16- [Dependencies](#org5589bef)
17- [Integration](#org7b599dd)
18- [License](#org56e2d0f)
19
20
21
22<a id="org77ab3e0"></a>
23
24# Overview
25
26\`flexoentity\` provides the **identity and lifecycle backbone** for all Flex-O components
27(Flex-O-Grader, Flex-O-Vault, Flex-O-Drill, …).
28
29It defines how entities such as questions, media, catalogs, and exams are **identified, versioned, signed, and verified** — all without any external database dependencies.
30
31At its heart lie two modules:
32
33- `id_factory.py` – robust, cryptographically-verifiable **Flex-O ID generator**
34- `flexo_entity.py` – abstract **base class for all versioned entities**
35
36Together, they form a compact yet powerful foundation for audit-ready, reproducible data structures across offline and air-gapped deployments.
37
38- Design Goals
39
40<table border="2" cellspacing="0" cellpadding="6" rules="groups" frame="hsides">
41
42
43<colgroup>
44<col class="org-left" />
45
46<col class="org-left" />
47</colgroup>
48<thead>
49<tr>
50<th scope="col" class="org-left">Goal</th>
51<th scope="col" class="org-left">Description</th>
52</tr>
53</thead>
54<tbody>
55<tr>
56<td class="org-left"><b>Determinism</b></td>
57<td class="org-left">IDs are derived from canonicalized entity content — identical input always yields identical ID prefix.</td>
58</tr>
59
60<tr>
61<td class="org-left"><b>Integrity</b></td>
62<td class="org-left">BLAKE2s hashing and digital signatures protect against manual tampering.</td>
63</tr>
64
65<tr>
66<td class="org-left"><b>Traceability</b></td>
67<td class="org-left">Version numbers (<code>@001A</code>, <code>@002S</code>, …) track entity lifecycle transitions.</td>
68</tr>
69
70<tr>
71<td class="org-left"><b>Stability</b></td>
72<td class="org-left">Hash prefixes remain constant across state changes; only version and state suffixes evolve.</td>
73</tr>
74
75<tr>
76<td class="org-left"><b>Auditability</b></td>
77<td class="org-left">Every entity can be serialized, verified, and reconstructed without hidden dependencies.</td>
78</tr>
79
80<tr>
81<td class="org-left"><b>Simplicity</b></td>
82<td class="org-left">Pure-Python, zero external libraries, self-contained and easy to embed.</td>
83</tr>
84</tbody>
85</table>
86
87
88<a id="orgf136db1"></a>
89
90# Flex-O ID Structure
91
92Each entity carries a unique **Flex-O ID**, generated by `FlexOID.generate()`.
93
94 AF-I250101-9A4C2D@003S
95
96<table border="2" cellspacing="0" cellpadding="6" rules="groups" frame="hsides">
97
98
99<colgroup>
100<col class="org-left" />
101
102<col class="org-left" />
103
104<col class="org-left" />
105</colgroup>
106<thead>
107<tr>
108<th scope="col" class="org-left">Segment</th>
109<th scope="col" class="org-left">Example</th>
110<th scope="col" class="org-left">Meaning</th>
111</tr>
112</thead>
113<tbody>
114<tr>
115<td class="org-left"><b>Domain</b></td>
116<td class="org-left"><code>AF or PY_LANG</code></td>
117<td class="org-left">Uppercase - Logical scope (e.g. &ldquo;Air Force&rdquo;)</td>
118</tr>
119
120<tr>
121<td class="org-left"><b>Type</b></td>
122<td class="org-left"><code>I</code></td>
123<td class="org-left">Entity type (e.g. ITEM)</td>
124</tr>
125
126<tr>
127<td class="org-left"><b>Date</b></td>
128<td class="org-left"><code>250101</code></td>
129<td class="org-left">UTC creation date (YYMMDD)</td>
130</tr>
131
132<tr>
133<td class="org-left"><b>Hash</b></td>
134<td class="org-left"><code>9A4C2D4F6E53</code></td>
135<td class="org-left">12-digit BLAKE2s digest of canonical content</td>
136</tr>
137
138<tr>
139<td class="org-left"><b>Version</b></td>
140<td class="org-left"><code>003</code></td>
141<td class="org-left">Sequential version counter</td>
142</tr>
143
144<tr>
145<td class="org-left"><b>State</b></td>
146<td class="org-left"><code>S</code></td>
147<td class="org-left">Lifecycle state (D, A, S, P, O)</td>
148</tr>
149</tbody>
150</table>
151
152
153<a id="orgea1c2ca"></a>
154
155# Lifecycle States
156
157<table border="2" cellspacing="0" cellpadding="6" rules="groups" frame="hsides">
158
159
160<colgroup>
161<col class="org-left" />
162
163<col class="org-left" />
164
165<col class="org-left" />
166</colgroup>
167<thead>
168<tr>
169<th scope="col" class="org-left">State</th>
170<th scope="col" class="org-left">Abbrev.</th>
171<th scope="col" class="org-left">Description</th>
172</tr>
173</thead>
174<tbody>
175<tr>
176<td class="org-left"><b>DRAFT</b></td>
177<td class="org-left"><code>D</code></td>
178<td class="org-left">Editable, not yet validated</td>
179</tr>
180
181<tr>
182<td class="org-left"><b>APPROVED</b></td>
183<td class="org-left"><code>A</code></td>
184<td class="org-left">Reviewed and accepted</td>
185</tr>
186
187<tr>
188<td class="org-left"><b>APPROVED<sub>AND</sub><sub>SIGNED</sub></b></td>
189<td class="org-left"><code>S</code></td>
190<td class="org-left">Cryptographically signed</td>
191</tr>
192
193<tr>
194<td class="org-left"><b>PUBLISHED</b></td>
195<td class="org-left"><code>P</code></td>
196<td class="org-left">Released to consumers</td>
197</tr>
198
199<tr>
200<td class="org-left"><b>OBSOLETE</b></td>
201<td class="org-left"><code>O</code></td>
202<td class="org-left">Archived or replaced</td>
203</tr>
204</tbody>
205</table>
206
207Transitions follow a strict progression:
208
209 DRAFT -> APPROVED -> APPROVED_AND_SIGNED -> PUBLISHED -> OBSOLETE
210
211Only DRAFT entities can be deleted - all others got OBSOLETE mark instead
212
213
214<a id="org2b0320a"></a>
215
216# Core Classes
217
218
219<a id="org39ec589"></a>
220
221## FlexOID
222
223A lightweight immutable class representing the full identity of an entity.
224
225**Highlights**
226
227- safe<sub>generate</sub>(domain, entity<sub>type</sub>, estate, text, version=1, repo) -> create a new ID
228- next<sub>version</sub>(oid) -> increment version safely
229- clone<sub>new</sub><sub>base</sub>(domain, entity<sub>type</sub>, estate, text) -> start a new lineage
230- Deterministic prefix, state-dependent signature
231
232
233<a id="orgd84c86d"></a>
234
235## `FlexoEntity`
236
237Abstract base class for all versioned entities (e.g., Question, Exam, Catalog).
238
239Implements:
240
241- ID lifecycle management (approve(), sign(), publish(), obsolete())
242- Serialization (to<sub>json</sub>(), from<sub>json</sub>(), to<sub>dict</sub>(), from<sub>dict</sub>())
243- Integrity verification (verify<sub>integrity</sub>(entity))
244- Controlled state transitions with automatic timestamps
245
246Subclasses define a single property:
247
248 @property
249 def text_seed(self) -> str:
250 """Canonical text or core content for hashing."""
251
252
253<a id="org4c3e14b"></a>
254
255# Integrity Verification
256
257Each entity can self-verify its integrity:
258
259 entity = Question.with_domain_id(domain_id="AF", text="What is Ohm’s law?", topic="Electronics")
260 json_str = entity.to_json()
261 reloaded = Question.from_json(json_str)
262
263 assert FlexoEntity.verify_integrity(reloaded)
264
265If the file is tampered with (e.g. &ldquo;Ohm’s&rdquo; → &ldquo;Omm’s&rdquo;), verification fails:
266
267
268<a id="orgfb82c02"></a>
269
270# Real World Example
271
272Below you can see the implementation of a dedicated FlexoEntity class, used for Domains.
273We set an ENTITY<sub>TYPE</sub> and define the needed fields in the data class. We define how to create
274a default object, the text<sub>seed</sub> (it is easy because the domain id is unique and therefore sufficient)
275and the methods for serialization.
276
277 from uuid import UUID
278 from dataclasses import dataclass
279 from flexoentity import FlexOID, FlexoEntity, EntityType
280
281 @dataclass
282 class Domain(FlexoEntity):
283 """
284 I am a helper class to provide more information than just a
285 domain abbreviation in FlexOID, doing mapping and management
286 """
287
288 ENTITY_TYPE = EntityType.DOMAIN
289
290 fullname: str = ""
291 description: str = ""
292 classification: str = "UNCLASSIFIED"
293
294 @classmethod
295 def default(cls):
296 """Return the default domain object."""
297 return cls.with_domain_id(domain_id="GEN_GENERIC",
298 fullname="Generic Domain", classification="UNCLASSIFIED")
299
300 @property
301 def text_seed(self) -> str:
302 return self.domain_id
303
304 def to_dict(self):
305 base = super().to_dict()
306 base.update({
307 "flexo_id": self.flexo_id,
308 "domain_id": self.domain_id,
309 "fullname": self.fullname,
310 "description": self.description,
311 "classification": self.classification,
312 })
313 return base
314
315 @classmethod
316 def from_dict(cls, data):
317 # Must have flexo_id
318 if "flexo_id" not in data:
319 raise ValueError("Domain serialization missing 'flexo_id'.")
320
321 flexo_id = FlexOID(data["flexo_id"])
322
323 obj = cls(
324 fullname=data.get("fullname", ""),
325 description=data.get("description", ""),
326 classification=data.get("classification", "UNCLASSIFIED"),
327 flexo_id=flexo_id,
328 _in_factory=True
329 )
330
331 # Restore metadata
332 obj.origin = data.get("origin")
333 obj.fingerprint = data.get("fingerprint", "")
334 obj.originator_id = (
335 UUID(data["originator_id"]) if data.get("originator_id") else None
336 )
337 obj.owner_id = (
338 UUID(data["owner_id"]) if data.get("owner_id") else None
339 )
340
341 return obj
342
343
344<a id="orge03e624"></a>
345
346# Usage
347
348 d = Domain.default()
349 print(d.flexo_id) # GEN_GENERIC-D251124-67C2CAE292CE@001D
350 d.approve()
351 print(d.flexo_id) # GEN_GENERIC-D251124-67C2CAE292CE@001A
352 d.sign()
353 print(d.flexo_id) # GEN_GENERIC-D251124-67C2CAE292CE@001S
354
355
356<a id="org118d77d"></a>
357
358# Serialization Example
359
360 {
361 'flexo_id': FlexOID(GEN_GENERIC-D251124-29CE0F4BE59D@001S),
362 'fingerprint': '534BD2EC5C5511F1',
363 'origin': FlexOID(GEN_GENERIC-D251124-67C2CAE292CE@001D),
364 'originator_id': '00000000-0000-0000-0000-000000000000',
365 'owner_id': '00000000-0000-0000-0000-000000000000',
366 'domain_id': 'GEN_GENERIC',
367 'fullname': 'Generic Domain',
368 'description': '',
369 'classification': 'UNCLASSIFIED'}
370
371 {
372 "flexo_id": "GEN_GENERIC-D251124-29CE0F4BE59D@001S",
373 "fingerprint": "534BD2EC5C5511F1",
374 "origin": "GEN_GENERIC-D251124-67C2CAE292CE@001D",
375 "originator_id": "00000000-0000-0000-0000-000000000000",
376 "owner_id": "00000000-0000-0000-0000-000000000000",
377 "domain_id": "GEN_GENERIC",
378 "fullname": "Generic Domain",
379 "description": "",
380 "classification": "UNCLASSIFIED"
381 }
382
383
384<a id="org83e21be"></a>
385
386# Entity Type and State Codes
387
388<table border="2" cellspacing="0" cellpadding="6" rules="groups" frame="hsides">
389
390
391<colgroup>
392<col class="org-left" />
393
394<col class="org-left" />
395
396<col class="org-left" />
397</colgroup>
398<thead>
399<tr>
400<th scope="col" class="org-left">EntityType</th>
401<th scope="col" class="org-left">Code</th>
402<th scope="col" class="org-left">Typical Use</th>
403</tr>
404</thead>
405<tbody>
406<tr>
407<td class="org-left">GENERIC</td>
408<td class="org-left">G</td>
409<td class="org-left">Generic entities that does not fit other types yet or are temporarily only</td>
410</tr>
411
412<tr>
413<td class="org-left">DOMAIN</td>
414<td class="org-left">D</td>
415<td class="org-left">Every Domain is of this type</td>
416</tr>
417
418<tr>
419<td class="org-left">MEDIA</td>
420<td class="org-left">M</td>
421<td class="org-left">Every media item belongs to this type, e.g. Pictures, Audio, Video</td>
422</tr>
423
424<tr>
425<td class="org-left">ITEM</td>
426<td class="org-left">I</td>
427<td class="org-left">An Entity what is usually used in a collection, e.g. Questions in a test</td>
428</tr>
429
430<tr>
431<td class="org-left">COLLECTION</td>
432<td class="org-left">C</td>
433<td class="org-left">A collection of items, as an Exam or a catalog</td>
434</tr>
435
436<tr>
437<td class="org-left">TEXT</td>
438<td class="org-left">T</td>
439<td class="org-left">A text document</td>
440</tr>
441
442<tr>
443<td class="org-left">HANDOUT</td>
444<td class="org-left">H</td>
445<td class="org-left">A published document</td>
446</tr>
447
448<tr>
449<td class="org-left">OUTPUT</td>
450<td class="org-left">O</td>
451<td class="org-left">The output of a computation</td>
452</tr>
453
454<tr>
455<td class="org-left">RECORD</td>
456<td class="org-left">R</td>
457<td class="org-left">Record type data, as bibliography entries</td>
458</tr>
459
460<tr>
461<td class="org-left">SESSION</td>
462<td class="org-left">S</td>
463<td class="org-left">A unique session, e.g. managed by a session manager</td>
464</tr>
465
466<tr>
467<td class="org-left">USER</td>
468<td class="org-left">U</td>
469<td class="org-left">User objects</td>
470</tr>
471
472<tr>
473<td class="org-left">CONFIG</td>
474<td class="org-left">F</td>
475<td class="org-left">CONFIG files that need to be tracked over time and state</td>
476</tr>
477
478<tr>
479<td class="org-left">EVENT</td>
480<td class="org-left">E</td>
481<td class="org-left">Events that have to be tracked over time, as status messages or orders</td>
482</tr>
483
484<tr>
485<td class="org-left">ATTESTATION</td>
486<td class="org-left">X</td>
487<td class="org-left">Entities that attest a formal technical (not human) check e.g. Signatures</td>
488</tr>
489</tbody>
490</table>
491
492<table border="2" cellspacing="0" cellpadding="6" rules="groups" frame="hsides">
493
494
495<colgroup>
496<col class="org-left" />
497
498<col class="org-left" />
499
500<col class="org-left" />
501</colgroup>
502<thead>
503<tr>
504<th scope="col" class="org-left">EntityState</th>
505<th scope="col" class="org-left">Code</th>
506<th scope="col" class="org-left">Meaning</th>
507</tr>
508</thead>
509<tbody>
510<tr>
511<td class="org-left">DRAFT</td>
512<td class="org-left">D</td>
513<td class="org-left">Work in progress</td>
514</tr>
515
516<tr>
517<td class="org-left">APPROVED</td>
518<td class="org-left">A</td>
519<td class="org-left">Reviewed</td>
520</tr>
521
522<tr>
523<td class="org-left">APPROVED<sub>AND</sub><sub>SIGNED</sub></td>
524<td class="org-left">S</td>
525<td class="org-left">Signed version</td>
526</tr>
527
528<tr>
529<td class="org-left">PUBLISHED</td>
530<td class="org-left">P</td>
531<td class="org-left">Publicly released</td>
532</tr>
533
534<tr>
535<td class="org-left">OBSOLETE</td>
536<td class="org-left">O</td>
537<td class="org-left">Deprecated</td>
538</tr>
539</tbody>
540</table>
541
542
543<a id="orga31954b"></a>
544
545# Design Notes
546
547- **Hash Stability:** Only domain, entity type, and content text influence the hash.
548 This ensures consistent prefixes across state changes.
549- **State-Dependent Signatures:** Each lifecycle stage has its own signature seed.
550 Modifying a file without re-signing invalidates integrity.
551- **Obsolescence Threshold:** Version numbers above 900 trigger warnings;
552 beyond 999 are considered obsolete.
553- **Clone Lineages:** Cloning an entity resets versioning but preserves metadata lineage.
554
555
556<a id="org5589bef"></a>
557
558# Dependencies
559
560- Python 3.11+
561- Standard library only (`hashlib`, `json`, `datetime`, `enum`, `dataclasses`)
562
563No external packages. Fully compatible with **Guix**, **air-gapped** deployments, and **reproducible builds**.
564
565
566<a id="org7b599dd"></a>
567
568# Integration
569
570\`flexoentity\` is imported by higher-level modules such as:
571
572- **Flex-O-Grader** → manages question catalogs and exam bundles
573- **Flex-O-Vault** → provides persistent media storage with metadata integrity
574- **Flex-O-Drill** → uses versioned entities for training simulations
575
576All share the same identity and versioning logic — ensuring that
577**what was approved, signed, and published remains provably authentic.**
578
579
580<a id="org56e2d0f"></a>
581
582# License
583
584MIT License 2025
585Part of the **Flex-O family** by Flex-O-Dyne GmbH
586Designed for reproducible, audit-ready, human-centered software.
587
Note: See TracBrowser for help on using the repository browser.