lardon3d/docs/architecture/persistence.md
fy59 b84f860d86 refactor(core): freeze global maintenance baseline
Complete the A-to-Z Lardon3D maintenance and coherence pass.

Generalize host resource policy, remove the global CPU12 ceiling, preserve
host CPU/RAM reserves, scale Task capabilities through the Resource Governor,
and validate deterministic parallel GV execution.

Migrate Project DB to v23 with data-driven camera, lens, optical configuration
and calibration profiles, including manual lenses without EXIF.

Integrate safe optional LARDON SSD swap/scratch control with Governor and F10
drain/safe-to-unplug semantics.

Refactor the ncurses TUI into a runtime observatory with durable progress,
elapsed time, smoothed ETA, throughput, resource telemetry, Governor state,
optics workflow, colors and compact/no-color fallbacks.

Reconcile Queue lifetime, persistence, concurrency, comments, tests,
README, AGENTS and canonical documentation.

GLOBAL_MAINTENANCE_AUDIT=PASS/FROZEN
2026-09-01 08:00:47 +02:00

244 lines
11 KiB
Markdown

# Persistance et base de données Lardon3D
## Vision
Lardon3D stocke les métadonnées de reconstruction dans Project DB SQLite,
tandis que les données numériques massives restent dans des fichiers/binaires
adaptés. Le schéma courant est v23 ; les sections v7 ci-dessous documentent la
fondation historique sans prétendre être la tête de migration.
## Principes fondamentaux
### Séparation logique/binaire
- État logique, relations, index → base persistante légère
- Données numériques massives → fichiers/artefacts binaires adaptés
### Cycle de publication
```
lot calculé
→ artefact temporaire
→ validation
→ publication atomique
→ transaction de métadonnées
→ état READY
```
### Règle de reprise
Une reprise ne considère jamais un artefact partiellement publié comme valide.
## Concepts de domaine
Les éléments suivants sont des concepts de domaine, PAS des tables SQL imposées :
- project
- scan_set
- image
- feature_set
- visual_signature
- candidate_pair
- verified_pair
- track
- observation
- camera
- camera_body_profile
- lens_profile
- optical_configuration
- optical_calibration_profile
- pose
- point3d
- reconstruction_layer
- measurement
- document_source
- geometric_constraint
- artifact
- checkpoint
## Invariants
- Chaque publication est atomique
- Les artefacts partiels ne sont jamais considérés comme valides
- La reprise commence à la dernière frontière connue
## Checkpoint durable de tâche v1
### État durable
Le modèle durable versionné contient uniquement l'identifiant stable, le nom,
l'estimation immuable, l'état observé, l'état de reprise, la progression, le
message, les horodatages et le compteur de séquences. Il ne contient aucun gros
artefact numérique. Une future version pourra référencer des identifiants
d'artefacts publiés et validés sans incorporer leur contenu.
Les mutex, conditions, callbacks, userdata, workers, gouverneur, réservations et
contrats d'exécution sont transitoires et ne sont jamais sérialisés.
### Normalisation après arrêt de processus
| État observé | État restauré |
|---|---|
| `TASK_PENDING` | `TASK_PENDING` |
| `TASK_RUNNING` | `TASK_PENDING` |
| `TASK_PAUSED` | `TASK_PENDING` |
| `TASK_COMPLETED` | `TASK_COMPLETED` |
| `TASK_FAILED` | `TASK_FAILED` |
| `TASK_CANCELLED` | `TASK_CANCELLED` |
Une rupture de séquence n'est pas un état : elle est observée comme
`TASK_RUNNING`. Son `sequence_count` est durable, mais la reprise revient à
`TASK_PENDING` et exige une nouvelle admission.
### Stockage minimal
Le codec v1 est indépendant de la future Project Database. Le fichier est de
taille fixe et bornée, encodé champ par champ, avec magie, version, taille et
checksum de payload.
La publication écrit un fichier temporaire unique dans le même répertoire,
effectue `fsync`, renomme atomiquement puis synchronise le répertoire parent.
La lecture distingue absence, corruption, version inconnue et erreur d'I/O.
La sauvegarde distingue trois frontières :
- avant `rename`, toute erreur retourne `IO_ERROR`, supprime le temporaire et
laisse l'ancien checkpoint publié inchangé ;
- après un `rename` réussi, le nouveau checkpoint est publié et visible et
n'est jamais présenté comme rollbackable ;
- si le `fsync` du répertoire échoue après ce `rename`, le résultat est
`PUBLISHED_NOT_DURABLE` : le fichier visible est valide, mais sa présence sous
ce nom après un crash ou une coupure n'est pas garantie. `OK` garantit que le
contenu et l'entrée de répertoire ont tous deux été synchronisés avec succès,
sous réserve des garanties fournies par le système de fichiers et le stockage.
Les tailles persistantes sont refusées avant conversion lorsqu'elles dépassent
`SIZE_MAX`. Les secondes sont des entiers non signés v1 : les timestamps
négatifs ne sont pas sérialisables et une valeur lue doit être représentable
par le `time_t` local avant conversion. Le format reste donc lisible entre
plateformes uniquement pour les valeurs communes à leurs domaines `size_t` et
`time_t`.
## Project Database v7 — fondation historique
SQLite contient l'état logique interrogable et les références aux fichiers ;
les checkpoints et artefacts volumineux restent externes. L'enregistrement du
résumé de tâche et de sa référence checkpoint est une transaction unique. Un
artefact est d'abord publié et vérifié comme fichier régulier, puis seulement
marqué `READY` en DB. Le chemin inverse est interdit.
### Protocole checkpoint projet
Le protocole réel n'est pas une transaction distribuée :
1. capture locale du snapshot sous le mutex de tâche puis déverrouillage ;
2. publication atomique du fichier sous
`.lardon3d/checkpoints/<task_id>.chk` ;
3. transaction SQLite sur `tasks` et `checkpoints` avec chemin relatif.
Une erreur avant publication ne modifie pas la DB. `PUBLISHED_NOT_DURABLE` est
conservé comme tel en DB. Si la publication réussit puis que SQLite retourne
`BUSY` ou une erreur, le fichier valide reste sur disque, la DB conserve son
ancienne vérité et le nouveau fichier est un orphelin à réconcilier plus tard.
Il n'est pas supprimé et aucune atomicité FS+SQLite n'est revendiquée.
L'inventaire distingue checkpoint récupérable durable, récupérable mais publié
non durable, absent, invalide, version inconnue et erreur d'I/O. Aucune réparation
ou suppression silencieuse n'est effectuée.
Le format checkpoint reste en version 1 et ne contient pas de `task_kind`. Le
schéma SQLite v4 conserve `task_kind` et `task_kind_version` dans le résumé
logique interrogable. La migration v1→v2 laisse ces deux colonnes à `NULL` : une
tâche legacy reste inspectable mais ne peut pas être reconstruite ou resoumise.
Un kind inconnu ou une version non supportée est diagnostiqué sans exécuter de
code.
## Statut
**IMPLEMENTED** — modèle durable, codec v1, lecture validée, publication
atomique et restauration sûre d'une tâche isolée.
**IMPLEMENTED** — Project Database v7 pour identité, tâches typées, ScanSets,
images logiques, Feature Sets/assets SHA-256, Visual Index segmenté,
checkpoints et artefacts génériques.
**IMPLEMENTED** — registry statique bornée et reconstruction explicite avec
ownership du userdata.
**IMPLEMENTED** — API projet de sauvegarde fichier+DB et inventaire validé au
redémarrage.
**IMPLEMENTED**`import.images` persiste son chemin source absolu et son
`scanset_id`, puis publie un checkpoint après chaque lot validé. Le catalogue
SQLite rend le rejeu idempotent à la granularité du contenu dans un ScanSet.
Le chemin source absolu est l'intention durable v1 : il doit rester accessible
après redémarrage et un projet déplacé ne rend pas une source externe portable.
Une source absente ou devenue non-répertoire fait échouer proprement la
reconstruction. Après import terminé, l'image dépend de l'asset géré, plus de la
source. Le SHA-256 est calculé pendant la copie avec un tampon fixe de 64 Kio.
L'asset est publié sans écrasement sous
`assets/images/<prefix>/<sha256>`, puis seulement enregistré `READY` dans une
transaction SQLite. Un asset concurrent déjà présent n'est adopté qu'après
rehash complet et vérification de taille. Si SQLite échoue après publication,
le fichier reste orphelin pour une future réconciliation ; aucune transaction
FS+SQLite n'est revendiquée.
Les identités publiées `scanset_id`, `image_id` et `asset_id` utilisent les
séquences SQLite `AUTOINCREMENT` : une valeur issue d'une transaction validée
n'est jamais réattribuée à un autre objet, même après suppression de la ligne.
Une valeur réservée par une transaction annulée n'est pas une identité publiée.
`manifest.tsv` reste supporté par l'ancien chemin d'import/catalogue. Le chemin
persistant entretient une projection best-effort par hardlinks pour la TUI,
mais SQLite est le commit logique canonique : la correction de la reprise ne
dépend plus de l'ordre de `readdir()` ni du nom de fichier.
La migration v3 vers v4 ne transforme pas les lignes du manifeste historique
en images cataloguées : elles ne contiennent pas toutes les preuves exigées par
le modèle v4. Le marqueur durable `legacy_image_catalog_pending` rend cette
situation visible. Une tâche récupérable peut repeupler le catalogue par rejeu
si sa source existe encore ; sinon les fichiers et le manifeste restent une
projection legacy, explicitement non cataloguée. Une tâche v3 déjà terminée
n'est pas rejouée automatiquement.
**IMPLEMENTED** — reprise automatique sélective à l'ouverture : pagination
bornée, validation checkpoint/kind, reconstruction production et enqueue sans
claim persistant supplémentaire.
Les records sont parcourus par task ID croissant. Un checkpoint
`PUBLISHED_NOT_DURABLE` présent, valide et cohérent peut être repris ; le résumé
conserve cet avertissement jusqu'au prochain checkpoint durable. Une tâche
terminale n'appartient pas à la requête de reprise.
**NOT_YET_WIRED** — réconciliation des fichiers orphelins et retry piloté par
l'utilisateur pour les sources indisponibles. Les checkpoints existants sont
kind-owned aux frontières métier ; aucun timer générique ne peut les remplacer.
**NOT_YET_WIRED** — migration de la vue TUI en mémoire vers la pagination
SQLite, scrub des assets et réconciliation globale des orphelins.
**IMPLEMENTED** — Feature Store externe versionné, immutable, borné et relié
transactionnellement à ses métadonnées SQLite après publication.
**IMPLEMENTED** — Visual Index externe segmenté, memberships transactionnels
et tâche `visual_index.update` récupérable.
**IMPLEMENTED** — migrations additives et séquentielles jusqu'à Project DB
v23. Les versions v16 à v22 restent l'histoire scientifique et de persistance
gelée ; v23 ajoute uniquement l'overlay optique générique.
Les neuf relations v23 séparent profils de boîtier et alias, profils d'objectif
et alias, configurations optiques, affectations de configuration aux groupes
de campagne et aux Captures, profils de calibration et sélection explicite par
Capture. Une configuration référence exactement un boîtier et un objectif ; un
objectif manuel sans EXIF est normal. La compatibilité d'une
calibration est exacte sur la configuration optique et ses dimensions/champs
scientifiques. Aucun profil S21, A6000 ou Meike n'est inséré ou déduit par la
migration : les tables nouvelles restent vides tant qu'un caller ne fournit
pas explicitement les données.
La migration v22→v23 est une transaction additive. Elle ne réinterprète ni les
Captures, ni les Images, ni les résultats scientifiques historiques. Une copie
S21 et une copie A6000 ont atteint v23 avec intégrité et clés étrangères
valides, comptes scientifiques inchangés et tables optiques vides. Les détails
normatifs sont dans [Project Database](project_database.md).
**NOT_YET_WIRED** — reprise ordonnée par dépendances/DAG et réconciliation
globale des artefacts orphelins.