source: flexoentity/README.md@ 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: 15.6 KB
Line 
1
2# Table of Contents
3
4- [Overview](#org8360697)
5- [Flex-O ID Structure](#org89396ad)
6- [Lifecycle States](#orgd522c8c)
7- [Core Classes](#org381a3f6)
8 - [FlexOID](#org6abdc86)
9 - [`FlexoEntity`](#org15b9543)
10- [Integrity Verification](#org1617277)
11- [Real World Example](#orge08e5d3)
12- [Usage](#orgc8e7dd2)
13- [Serialization Example](#org1b3bbed)
14- [Entity Type and State Codes](#org5dfc109)
15- [Design Notes](#org659c733)
16- [Dependencies](#orga67f3a6)
17- [Integration](#org10ab165)
18- [License](#org48ba119)
19
20
21
22<a id="org8360697"></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="org89396ad"></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="orgd522c8c"></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="org381a3f6"></a>
215
216# Core Classes
217
218
219<a id="org6abdc86"></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="org15b9543"></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="org1617277"></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="orge08e5d3"></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="orgc8e7dd2"></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="org1b3bbed"></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\#+BEGIN<sub>SRC</sub> js
372
373{
374 &ldquo;flexo<sub>id</sub>&rdquo;: &ldquo;GEN<sub>GENERIC</sub>-D251124-29CE0F4BE59D@001S&rdquo;,
375 &ldquo;fingerprint&rdquo;: &ldquo;534BD2EC5C5511F1&rdquo;,
376 &ldquo;origin&rdquo;: &ldquo;GEN<sub>GENERIC</sub>-D251124-67C2CAE292CE@001D&rdquo;,
377 &ldquo;originator<sub>id</sub>&rdquo;: &ldquo;00000000-0000-0000-0000-000000000000&rdquo;,
378 &ldquo;owner<sub>id</sub>&rdquo;: &ldquo;00000000-0000-0000-0000-000000000000&rdquo;,
379 &ldquo;domain<sub>id</sub>&rdquo;: &ldquo;GEN<sub>GENERIC</sub>&rdquo;,
380 &ldquo;fullname&rdquo;: &ldquo;Generic Domain&rdquo;,
381 &ldquo;description&rdquo;: &ldquo;&rdquo;,
382 &ldquo;classification&rdquo;: &ldquo;UNCLASSIFIED&rdquo;
383}
384
385
386<a id="org5dfc109"></a>
387
388# Entity Type and State Codes
389
390<table border="2" cellspacing="0" cellpadding="6" rules="groups" frame="hsides">
391
392
393<colgroup>
394<col class="org-left" />
395
396<col class="org-left" />
397
398<col class="org-left" />
399</colgroup>
400<thead>
401<tr>
402<th scope="col" class="org-left">EntityType</th>
403<th scope="col" class="org-left">Code</th>
404<th scope="col" class="org-left">Typical Use</th>
405</tr>
406</thead>
407<tbody>
408<tr>
409<td class="org-left">GENERIC</td>
410<td class="org-left">G</td>
411<td class="org-left">Generic entities that does not fit other types yet or are temporarily only</td>
412</tr>
413
414<tr>
415<td class="org-left">DOMAIN</td>
416<td class="org-left">D</td>
417<td class="org-left">Every Domain is of this type</td>
418</tr>
419
420<tr>
421<td class="org-left">MEDIA</td>
422<td class="org-left">M</td>
423<td class="org-left">Every media item belongs to this type, e.g. Pictures, Audio, Video</td>
424</tr>
425
426<tr>
427<td class="org-left">ITEM</td>
428<td class="org-left">I</td>
429<td class="org-left">An Entity what is usually used in a collection, e.g. Questions in a test</td>
430</tr>
431
432<tr>
433<td class="org-left">COLLECTION</td>
434<td class="org-left">C</td>
435<td class="org-left">A collection of items, as an Exam or a catalog</td>
436</tr>
437
438<tr>
439<td class="org-left">TEXT</td>
440<td class="org-left">T</td>
441<td class="org-left">A text document</td>
442</tr>
443
444<tr>
445<td class="org-left">HANDOUT</td>
446<td class="org-left">H</td>
447<td class="org-left">A published document</td>
448</tr>
449
450<tr>
451<td class="org-left">OUTPUT</td>
452<td class="org-left">O</td>
453<td class="org-left">The output of a computation</td>
454</tr>
455
456<tr>
457<td class="org-left">RECORD</td>
458<td class="org-left">R</td>
459<td class="org-left">Record type data, as bibliography entries</td>
460</tr>
461
462<tr>
463<td class="org-left">SESSION</td>
464<td class="org-left">S</td>
465<td class="org-left">A unique session, e.g. managed by a session manager</td>
466</tr>
467
468<tr>
469<td class="org-left">USER</td>
470<td class="org-left">U</td>
471<td class="org-left">User objects</td>
472</tr>
473
474<tr>
475<td class="org-left">CONFIG</td>
476<td class="org-left">F</td>
477<td class="org-left">CONFIG files that need to be tracked over time and state</td>
478</tr>
479
480<tr>
481<td class="org-left">EVENT</td>
482<td class="org-left">E</td>
483<td class="org-left">Events that have to be tracked over time, as status messages or orders</td>
484</tr>
485
486<tr>
487<td class="org-left">ATTESTATION</td>
488<td class="org-left">X</td>
489<td class="org-left">Entities that attest a formal technical (not human) check e.g. Signatures</td>
490</tr>
491</tbody>
492</table>
493
494<table border="2" cellspacing="0" cellpadding="6" rules="groups" frame="hsides">
495
496
497<colgroup>
498<col class="org-left" />
499
500<col class="org-left" />
501
502<col class="org-left" />
503</colgroup>
504<thead>
505<tr>
506<th scope="col" class="org-left">EntityState</th>
507<th scope="col" class="org-left">Code</th>
508<th scope="col" class="org-left">Meaning</th>
509</tr>
510</thead>
511<tbody>
512<tr>
513<td class="org-left">DRAFT</td>
514<td class="org-left">D</td>
515<td class="org-left">Work in progress</td>
516</tr>
517
518<tr>
519<td class="org-left">APPROVED</td>
520<td class="org-left">A</td>
521<td class="org-left">Reviewed</td>
522</tr>
523
524<tr>
525<td class="org-left">APPROVED<sub>AND</sub><sub>SIGNED</sub></td>
526<td class="org-left">S</td>
527<td class="org-left">Signed version</td>
528</tr>
529
530<tr>
531<td class="org-left">PUBLISHED</td>
532<td class="org-left">P</td>
533<td class="org-left">Publicly released</td>
534</tr>
535
536<tr>
537<td class="org-left">OBSOLETE</td>
538<td class="org-left">O</td>
539<td class="org-left">Deprecated</td>
540</tr>
541</tbody>
542</table>
543
544
545<a id="org659c733"></a>
546
547# Design Notes
548
549- **Hash Stability:** Only domain, entity type, and content text influence the hash.
550 This ensures consistent prefixes across state changes.
551- **State-Dependent Signatures:** Each lifecycle stage has its own signature seed.
552 Modifying a file without re-signing invalidates integrity.
553- **Obsolescence Threshold:** Version numbers above 900 trigger warnings;
554 beyond 999 are considered obsolete.
555- **Clone Lineages:** Cloning an entity resets versioning but preserves metadata lineage.
556
557
558<a id="orga67f3a6"></a>
559
560# Dependencies
561
562- Python 3.11+
563- Standard library only (`hashlib`, `json`, `datetime`, `enum`, `dataclasses`)
564
565No external packages. Fully compatible with **Guix**, **air-gapped** deployments, and **reproducible builds**.
566
567
568<a id="org10ab165"></a>
569
570# Integration
571
572\`flexoentity\` is imported by higher-level modules such as:
573
574- **Flex-O-Grader** → manages question catalogs and exam bundles
575- **Flex-O-Vault** → provides persistent media storage with metadata integrity
576- **Flex-O-Drill** → uses versioned entities for training simulations
577
578All share the same identity and versioning logic — ensuring that
579**what was approved, signed, and published remains provably authentic.**
580
581
582<a id="org48ba119"></a>
583
584# License
585
586MIT License 2025
587Part of the **Flex-O family** by Flex-O-Dyne GmbH
588Designed for reproducible, audit-ready, human-centered software.
589
Note: See TracBrowser for help on using the repository browser.