lardon3d/docs/architecture/runtime.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

314 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Exécution et runtime Lardon3D
## Modèle d'exécution
### Threads
- Thread principal : entrée, modèle de vue et rendu TUI ncursesw (exclusif)
- Thread worker Queue : exécution sérielle des tâches métier
- Participants internes : uniquement ceux du contrat Task admis, joints par le
callback propriétaire avant publication
- Opération SSD : au plus un thread joinable, uniquement pendant une opération
UDisks bornée ; il ne rend rien et ne devient ni Queue ni scheduler
### Synchronisation
- Mutex pour les accès partagés
- Variables de condition pour la coordination
- Atomicité des opérations critiques
## Cycle de vie d'une tâche
```text
1. Création (PENDING)
2. Soumission à la file
3. Sélection FIFO/adaptative par la Queue
4. Réservation obligatoire
5. Exécution (RUNNING)
- Pause/reprise coopérative
- Annulation coopérative
- Séquences adaptatives
6. Complétion (COMPLETED) ou Échec (FAILED)
7. Nettoyage des ressources
```
## Synchronisation
### Mutex
- Protection des données partagées
- Accès exclusif aux ressources critiques
### Variables de condition
- Coordination entre threads
- Notification de changement d'état
- Attente passive ; timeout borné seulement pour réévaluer un `WAIT` ressources
### Atomicité
- Opérations indivisibles
- État cohérent garanti
## Gestion des erreurs
### Rollback
- Rollback des transactions locales avant publication
- Nettoyage complet des ressources possédées par l'opération
- Une publication fichier réussie suivie d'un échec DB peut laisser un orphelin
valide ; aucune transaction distribuée fichier+SQLite n'est revendiquée
### Récupération
- Reprise à la dernière frontière connue
- Ignorance des artefacts partiels
- Validation avant publication
## Limites actuelles
- Worker Queue unique (pas de pools inter-Tasks multiples)
- Pas de parallélisme inter-Tasks ; certains kinds possèdent des participants
internes bornés, comptés par leur contrat Governor
- Reprise automatique limitée aux tâches indépendantes reconstructibles
## Reprise durable
Un snapshot ne conserve que l'état logique d'une tâche. `RUNNING` et `PAUSED`
sont normalisés vers `PENDING`; aucun worker, callback brut, pointeur, contrat
ou réservation n'est restauré. Le propriétaire fournit un nouveau callback et
resoumet la tâche. Les états terminaux sont conservés.
`started_at` désigne le début de la tentative d'exécution courante, pas le
premier démarrage historique. Un checkpoint `RUNNING` restauré en `PENDING`
conserve temporairement l'horodatage de la tentative interrompue pour
l'observation ; lors de `lardon3d_task_start()`, `started_at` est remplacé par le
nouveau démarrage et `finished_at` est remis à zéro. `finished_at` n'est fixé
qu'à la terminaison de cette tentative.
**IMPLEMENTED** — snapshot, codec v1 et restauration isolée.
**IMPLEMENTED** — l'import `import.images` se sauvegarde à chaque fin de lot et
se reconstruit explicitement avec un userdata neuf lié au projet rouvert. Son
intention durable contient `source_path + scanset_id`; le hash/copie et la
transaction catalogue restent hors mutex Task et hors mutex DB pendant l'I/O.
**IMPLEMENTED**`project_open()` inventorie par pages de 8, restaure puis
resoumet automatiquement les tâches production valides. Il retourne après
l'enqueue et n'attend jamais leur terminaison.
L'ordre d'initialisation production est : politique driver, profil matériel,
Governor, backend, Queue/worker, contrôleur SSD optionnel et binding
Governor, puis TUI. L'ouverture DB/projet et la reprise synchrone sont pilotées
ensuite depuis le thread principal. Une fermeture ne peut commencer qu'après le
retour de `project_open()`. Le worker peut consommer pendant le scan ; chaque
tâche exécutée est néanmoins réadmise normalement.
**NOT_YET_WIRED** — reprise ordonnée par dépendances/DAG. Les kinds de
production reconstructibles checkpointent déjà à leurs frontières métier ;
aucun timer autosave générique ne doit avancer devant leur publication durable.
**IMPLEMENTED** — reprise sélective des kinds reconstructibles via Project DB,
Task Kind Registry et Queue. Les dépendances/DAG restent différées ; il
n'existe pas de scheduler global distinct à restaurer.
## Accès Project Database
**IMPLEMENTED** — une connexion SQLite opaque sérialisée par mutex interne ;
les opérations multi-tables sont transactionnelles et bornées.
**IMPLEMENTED** — le cycle de vie projet ouvre/crée `project.db`, vérifie
l'identité et ferme la connexion. Ouvrir, fermer ou changer de projet est une
frontière exacte : l'observateur et la vue optique libèrent leurs borrows, puis
l'unique Queue est annulée, jointe et détruite, callbacks terminaux inclus,
avant la fermeture de Project DB. Une seule Queue vide est ensuite recréée et
les observateurs sont rebondés. Il n'existe jamais deux schedulers simultanés.
L'historique terminal et l'espace d'IDs Queue sont ainsi propres à la session ;
les mêmes Task IDs durables de deux projets restent indépendants et aucun
historique fourni n'est affiché lorsqu'aucun projet n'est chargé.
**IMPLEMENTED** — la registry reconstruit explicitement callback/userdata hors
mutex DB pour un kind connu ; elle ne soumet aucune tâche.
**IMPLEMENTED** — la queue accepte un identifiant restauré préassigné s'il
n'entre en collision avec aucune tâche connue. L'import production peut donc
être reconstruit puis soumis explicitement.
**IMPLEMENTED** — la resoumission automatique utilise la registry production,
conserve le task ID et laisse le worker obtenir une nouvelle réservation.
Kinds inconnus, tâches legacy, checkpoints invalides et sources absentes ne
bloquent pas l'ouverture.
**IMPLEMENTED**`visual_index.update` reprend à la dernière membership
commitée. Un segment temporaire n'est jamais visible et un rejeu exclut les
Feature Sets déjà membres.
## Durée de vie terminale et fermeture Queue
Une Task terminale reste vivante jusqu'au retour complet de son callback
terminé. Queue la retire alors de la liste active et la détruit hors de son
mutex ; seule une histoire de 64 snapshots reste observable. Les appels déjà
enregistrés avant `task_queue_destroy()` sont attendus. Le propriétaire doit
empêcher tout nouvel appel dès le début de la destruction, règle nécessaire à
toute API C adressée par pointeur brut.
Un callback terminé peut consulter les vues Queue tant que le propriétaire la
maintient vivante. Il ne peut pas détruire cette Queue, retirer son propre
record ni attendre une opération dépendante de son retour.
## Observatoire TUI actuel
**CURRENT / VALIDATED OPERATIONAL.** Ce statut décrit l'implémentation et ses
tests courants. L'audit global qui contient cette frontière est désormais
`PASS/FROZEN` après revue indépendante ; le statut TUI reste volontairement
opérationnel et n'interdit pas ses évolutions futures sous un ticket distinct.
### Séparation modèle, observation et rendu
Le modèle `tui_model` est pur et testable sans terminal. Le renderer reçoit
seulement des copies bornées et n'interroge ni Queue, ni Governor, ni Project
DB, ni contrôleur SSD. Toutes les fonctions ncurses, l'entrée clavier et le
rendu demeurent sur le thread principal.
L'observateur runtime emprunte Queue et Governor et conserve une seule copie
cohérente. Les captures ordinaires sont coalescées pendant au moins une seconde
monotone ; un échec conserve la dernière vue bornée en la marquant stale.
Il observe au plus 129 Tasks : les 64 pending possibles, l'unique active et les
64 snapshots terminaux récents. L'ordre Queue place le travail vivant du plus
récent au plus ancien, puis l'histoire par terminaison décroissante ; une Task
active ne peut donc pas être masquée par un vieux préfixe historique. Il
n'existe ni scan DB par frame, ni lecture `/proc` volumineuse, ni historique
non borné.
Les ABI historiques restent exactes : `Lardon3DTaskSnapshot`,
`Lardon3DResourceSnapshot`, `Lardon3DAppState` et
`lardon3d_layout_draw()` ne sont pas étendus en place. Les surfaces additives
`Lardon3DTaskObservation`, `lardon3d_task_queue_observe()`,
`Lardon3DResourceObservation`, `Lardon3DRuntimeSnapshot` et
`lardon3d_layout_draw_runtime()` portent les nouveaux champs. De même,
`lardon3d_tui_run()` reste le symbole historique ; l'application utilise
`lardon3d_tui_run_with_ssd_operation()` avec un owner SSD conservé hors de
`Lardon3DAppState`.
### Progression et ETA
Une Task typée publie `completed/total` seulement après son propre commit
métier durable. Quand ces compteurs sont connus, la TUI les affiche toujours et
en dérive le pourcentage sans utiliser le message ou le nom. Une Task marquée
`COMPLETED` avec un préfixe durable incomplet est une erreur d'intégrité
visible, jamais 100 %. Quand les comptes typés sont inconnus, le lifecycle peut
être terminal mais la progression scientifique reste indéterminée. Le
pourcentage générique non typé, lorsqu'il est utile, porte explicitement le
libellé runtime.
Le débit est un EWMA borné. La première observation établit seulement le
préfixe de reprise et ne contribue pas au taux ; une reprise de RUNNING remet
également la fenêtre temporelle à zéro. Deux intervalles strictement positifs
sont nécessaires avant un débit et une ETA connus. Une absence de progrès,
une pression Governor, une régression ou une preuve insuffisante produit
respectivement `STALLED`, `THROTTLED`, reset ou `INDETERMINATE/CALCULATING`.
Seule une complétion cohérente vaut exactement 100 % et ETA zéro ; aucune fausse
précision n'est affichée.
### Pipeline et ressources
La synthèse utilise les étapes Acquisition, RAW, Quality, Features, Visual
Index, Candidate, Matcher, GV, Tracks, Sparse SfM et future Dense. Les états
sont `NOT_READY`, `READY`, `QUEUED`, `RUNNING`, `THROTTLED`, `BLOCKED`,
`COMPLETE`, `FAILED` et `NOT_APPLICABLE`. Dense reste explicitement
`NOT_APPLICABLE` tant qu'aucun Task kind de production n'existe ; une étape
future n'est jamais devinée active depuis un nom ou un message.
Le panneau ressources expose CPU actif/admis/disponible et sa raison, GPU
présent/mémoire/busy/backend lorsqu'ils sont connus, RAM/MemAvailable/réserve,
swap total/utilisé et deltas actifs, lot/inflight/helpers/I/O, scratch et
pression Governor GREEN/YELLOW/RED. Le contrat installé de l'exacte Task active
est l'autorité pour CPU et lot. Un dernier diagnostic privé seulement indexé
par kind peut appartenir à une autre Task ou séquence : backend, inflight,
helpers, utilisation ou raison restent donc `UNKNOWN` sans association exacte
Task+séquence. La mémoire UMA est comptée une seule fois et ni swap ni scratch
ne sont ajoutés à la capacité RAM.
### Dimensions, couleurs et clavier
Le layout complet demande au moins 100×30. Le layout compact est validé à la
frontière 72×20 et reste supporté jusqu'au minimum 60×15. En dessous, le rendu
se réduit au message borné `Terminal trop petit`; un resize recalcule la classe
sans faire travailler un worker. Les rôles sémantiques sont healthy vert,
warning jaune, error rouge, GPU cyan, CPU bleu, SSD magenta, plus dim/bold.
Les libellés textuels demeurent l'autorité lorsqu'il n'y a pas de couleur ou
pas assez de paires terminal.
Les écrans courants sont accueil, projets, import, viewer futur, tâches,
ressources, optique, SSD et aide. `F1..F7` naviguent respectivement vers aide,
projets, import, viewer, tâches, ressources et optique. Le segment littéral
`F10 SSD` est réservé au début du footer et reste visible à 60 colonnes dans
tous les modes pertinents. Les footers dérivent du même mode que le handler :
- saisie active : Enter valide, Échap annule, F10 reste disponible ;
- import actif : `X` demande l'annulation et F10 reste disponible ; `q` et
Échap sont affichés comme désactivés ;
- mode idle : `q`, Échap/navigation et les commandes propres à l'écran sont
annoncés seulement lorsqu'ils sont réellement traités ;
- Tasks : flèches/`j`/`k`, `P` pause, `R` reprise, `C` annulation ;
- Optique : Tab change de panneau, flèches/`j`/`k` sélectionnent, `[` revient à
la première page et `]` charge la suivante ; `B/L/C/V/A/G/K/E` déclenchent
les opérations indiquées et `R` retente explicitement un bind/chargement.
### Workflow optique
La TUI consomme les API v23 décrites dans
[Project Database](project_database.md), sans SQL direct ni édition d'une ligne
immuable. Elle inspecte une affectation Capture, effectue seulement des lookup
metadata exacts, liste les profils de boîtier/objectif/configuration et accepte
un objectif manuel sans électronique ni alias — le Meike de test est un cas
normal, pas une branche produit. « Modifier » signifie créer un nouveau profil
ou une nouvelle configuration immuable, puis l'assigner explicitement à un
groupe de campagne encore éligible ou à un Capture non affecté. Les
calibrations listées doivent être compatibles avec l'exacte configuration et
la sélection reste explicite ; absence, ambiguïté, incompatibilité, BUSY, I/O
et corruption sont rendues sans profil fabriqué. Les pages ont 16 lignes,
rapportent un compte page-local et un indicateur « suite » exact.
### SSD F10 et lifetime application
La TUI affiche les huit états physiques `ABSENT`, `DETECTED`, `ENABLING`,
`ENABLED`, `IN_USE`, `DRAINING`, `SAFE_TO_UNPLUG` et `ERROR`, avec identité
stable, modèle/télémétrie lorsqu'ils sont connus, swap, scratch, mount, usage,
leases, drain et raison. `UNKNOWN` n'est jamais remplacé par zéro ou par une
supposition ; `SAFE_TO_UNPLUG` est mis en évidence comme endpoint sûr. F10
choisit exclusivement l'une des capacités exactes
`can_enable`, `can_disable` ou `can_cancel_drain` publiée par le contrôleur ; un
état incomplet, une paire de remplacement ou un résultat malformé n'accorde
aucune autorité. L'opération synchrone UDisks s'exécute dans au plus un thread
joinable, tandis que le main continue de rendre et de poller sans blocage.
La validation qui alimente ces capacités est fail-closed par état : toute
autorité exige Drive et deux partitions détectés, identités Drive+UUID exactes,
extents positifs connus et faits mount/activité/drain cohérents. `ABSENT` ne
peut transporter aucun fait actif, `DETECTED` partiel n'a aucune action et un
hazard `ERROR` déconnecté ne peut que retenir l'identité originale sans
allocation. Seule la reconnexion complète de ce tuple peut autoriser son drain.
Après chaque observation ou résultat validé, l'adaptateur enregistre une copie
bornée de l'état physique auprès du Governor. Une copie malformée devient
`ERROR` et interdit les nouvelles allocations ; l'observation ressources lit
cet état Governor-owned, tandis que les détails/permissions F10 restent dans
le snapshot physique. La génération source peut saturer à `UINT64_MAX` : une
update publique égale ne réaccorde jamais une autorité stale ; seule la
complétion du wrapper exact déjà engagé réconcilie son lease adressé. À l'arrêt,
l'ordre est : destruction/join de la Queue et
libération de chaque lease Task, fermeture du projet/DB, join puis unregister
vérifié de l'adaptateur SSD, destruction du contrôleur, puis destruction du
Governor. Les tests utilisent un provider factice et n'exécutent aucune vraie
mutation SSD.
## Invariants
- ncurses appartient exclusivement au thread principal
- Aucune tâche ne démarre sans réservation active
- Les réservations sont libérées exactement une fois
- Les buffers sont strictement bornés
- Le Resource Governor reste l'unique propriétaire de l'admission ; ni Queue,
ni contrôleur SSD ne constituent un second orchestrateur de ressources
## Statut : CURRENT / VALIDATED OPERATIONAL
La TUI/runtime et son raccordement SSD sont implémentés, testés et relus dans
leur tranche. Le statut global est
`GLOBAL_MAINTENANCE_AUDIT=PASS/FROZEN`. Les builds portables/Vulkan,
sanitizers, contrôles de concurrence et ABI frais sont acquis ; l'unique revue
finale indépendante a conclu PASS sans finding bloquant.