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

30 KiB
Raw Permalink Blame History

Global maintenance audit — état consolidé

Lifecycle et portée

GLOBAL_MAINTENANCE_AUDIT=PASS/FROZEN.

Les tranches décrites ci-dessous, y compris l'observatoire TUI, F10 et le raccordement SSD/Governor, sont implémentées, validées opérationnellement et relues. Les builds Clang frais portable et Vulkan, les suites 64/64 et 65/65, les sanitizers applicables, TSan, les probes de headers publics et d'ABI sont acquis. L'unique revue finale indépendante GPT-5.6 SOL/ULTRA du dépôt consolidé a ensuite conclu PASS sans finding bloquant. La gate de maintenance est donc fermée et gelée ; ce statut ne réinterprète aucune science FROZEN et n'exécute pas la tranche scientifique suivante.

Ce record réconcilie l'état courant sans rouvrir les contrats scientifiques FROZEN. Les détails normatifs restent dans les documents de domaine :

Positionnement produit et identité des preuves

Lardon3D est un moteur de photogrammétrie générique, persistant et sensible aux ressources. Sony A6000, Samsung S21 FE, Ryzen 7 8845HS et Radeon 780M sont des fixtures, corpus ou hôtes de validation. Ils ne sont ni une identité produit, ni une branche de code métier, ni une limite portable de caméra, CPU ou GPU.

Les identités restent celles des contrats existants : Capture, Asset, image_id, Task ID, groupe de campagne, profil de boîtier, profil d'objectif, configuration optique et calibration sont distincts. Un chemin, basename, SHA-256, champ EXIF ou modèle d'appareil n'est jamais promu implicitement en une autre identité.

Project Database v23 et contexte optique générique

La tête de schéma courante est Project DB v23. La v23 est une migration additive au-dessus de la fondation scientifique et de persistance v22 gelée. Elle ne réécrit aucune ligne scientifique v16v22 et crée neuf tables vides :

Table v23 Concept possédé
camera_body_profiles profil de boîtier explicite
camera_body_aliases alias metadata make/model exact, binaire
lens_profiles profil d'objectif manuel, électronique ou intégré
lens_profile_aliases alias metadata d'objectif exact et optionnel
optical_configurations boîtier + objectif + focale exacte optionnelle
acquisition_campaign_group_optics configuration explicite d'un groupe de campagne
capture_optical_configurations configuration retenue pour un Capture et sa provenance
optical_calibration_profiles calibration existante compatible avec une configuration exacte
capture_calibration_selections sélection explicite de calibration pour un Capture

La migration v22→v23 s'exécute dans la transaction de migration existante. Le marqueur de version n'est publié qu'après création de toutes les tables ; tout échec rollbacke l'ensemble. Elle n'infère et ne backfill aucun profil depuis EXIF, chemin, nom, SHA-256, dimensions, calibration historique ou marque d'appareil. Une base v22 migre donc avec les neuf tables optiques vides.

Un objectif manuel sans EXIF est un cas normal : un profil explicite peut ne posséder aucun alias metadata. Le Meike manuel utilisé dans les tests est une preuve de ce chemin générique, pas un modèle hardcodé. Une campagne peut assigner des configurations différentes à ses groupes ; lors de la rétention du Capture, le binding campagne/configuration et l'avancement du curseur sont atomiques. Un Capture importé hors campagne peut recevoir une affectation explicite distincte, sans valeur « inconnue » fabriquée.

Une calibration optique référence une calibration sparse immuable existante et une seule configuration exacte. Il n'existe ni interpolation, ni fallback par focale proche, ni sélection automatique. Le Capture et le profil doivent référencer la même configuration ; plusieurs profils compatibles restent ambigus jusqu'à une sélection explicite. Les retries exacts convergent, les conflits valides sont des contraintes et les dépendances durables malformées sont de la corruption.

Les migrations exercées via l'API production sur des copies des projets S21 et A6000 ont atteint v23 avec integrity_check et clés étrangères propres, comptes scientifiques inchangés et tables optiques vides. Les SHA-256 des projets source sont restés inchangés. La validation v23 retenue comprend la suite normale 57/57, dix tests focalisés, cinq cibles ASan/UBSan, les contrôles de headers C17 et C++, les scénarios de rollback/retry et une revue indépendante après correction. Cette preuve ne réinterprète aucune calibration réelle des deux campagnes.

Resource Governor portable

Le Resource Governor demeure l'unique propriétaire de l'admission RAM, CPU, GPU et I/O et le seul orchestrateur des leases scratch de production. Le contrôleur SSD décrit plus bas est une frontière physique ; il n'est ni un scheduler, ni un second Governor, ni une source de capacité RAM.

La politique hôte courante réserve d'abord le système :

  • quatre CPU logiques lorsque l'hôte le permet, tout en conservant au moins un CPU de calcul sur un petit hôte ;
  • quand la topologie est fiable, un ensemble déterministe de coeurs physiques complets, frères SMT inclus, avec le dépassement minimal de la cible logique ;
  • les CPU déjà exclus par une affinité externe comptent dans la réserve hôte, afin de ne pas soustraire deux fois la même capacité ;
  • sans topologie ou masque fiable, le fallback ne fabrique aucun ID ni masque : il conserve seulement un budget de compte portable ;
  • environ 3 Gio de MemAvailable comme réserve dure sur un hôte capable ; la bande 34 Gio est une prudence YELLOW qui bloque la croissance mais ne soustrait pas 4 Gio à la capacité ;
  • sur un petit hôte RAM, une réserve fractionnaire déterministe permet une dégradation sûre sans prétendre conserver 3 Gio inexistants ;
  • PSI CPU/mémoire/I/O et deltas actifs de swap pilotent la pression ; l'occupation historique du swap ne suffit pas ;
  • la mémoire UMA est débitée exactement une fois et swap/zram/SSD ne deviennent jamais un objectif de travail ni une extension de la RAM admise.

Il n'existe plus de plafond CPU global à 12. Le Governor borne chaque contrat par le compute-pool réel et par la capacité intrinsèque du Task kind. Les IDs 0-5,8-13 et la réserve 6,7,14,15 restent la preuve historique correcte de l'hôte 16-CPU validé, pas une politique portable.

Capacités des quatorze Task kinds

La matrice chiffrée complète appartient au Resource Governor. La classification courante est :

Kind v1 CPU et lot courants Nature de la borne
raw.develop CPU 1, lot 1 opération atomique ; OpenCV global appliqué/restauré, allowance RAW 2 Gio
photo_quality.triage CPU 1, lot 1 un groupe borné ; aucune preuve de scaling utile
acquisition_campaign.run CPU 1, lot 1 orchestration d'un groupe ; coût JPEG/RAW exact, aucun Task imbriqué
import.images CPU 1, lot 1..32 copie/hash I/O-bound, réadmission par lot
features.extract CPU 1..compute-pool, lot 1 API OpenCV positive-int, plafond hôte Governor
features.extract.sift CPU 1..compute-pool, lot 1 même contrat OpenCV ; historique CPU12/CPU1 accepté exactement
features.extract.rootsift CPU 1..compute-pool, lot 1 même contrat OpenCV ; aucune voie GPU validée
visual_index.update CPU 1..16, lot 1..16 maximum algorithmique du segment de 16 Feature Sets
candidate_pair.generate CPU 1..64, lot 1..64 maximum algorithmique/ressource d'un batch de 64 sources
matcher.run CPU sûr 1..12 ; lot AUTO utile 1..8, sûr 1..12 batch Matcher mesuré ; Vulkan ORB exact, SIFT/RootSIFT CPU
geometric_verifier.run CPU utile 1..8, fenêtre CPU sûre 16, lot 1..16 outer-parallelism opérationnel ; USAC interne reste isParallel=false
track_builder.run CPU 1, lot 1 rebuild DSU complet, publication atomique owner-only
sparse_sfm.run CPU 1, lot 1 contrat scientifique/atomique FROZEN, BA à un thread
incremental_reconstruction.run CPU 1, lot 1 recomputation atomique FROZEN depuis entrées immuables

Candidate utilise au plus cpu_threads-1 enfants et un handle DB privé par participant, le callback Queue étant lui-même participant. Ses piles et états sont facturés à 8 Mio par item ; batch=1 ne crée pas de travail vide. Visual Index conserve ses buffers de segment fixes et publie seul après toutes les jointures. ORB/SIFT/RootSIFT utilisent INT_MAX seulement comme borne de l'API OpenCV : l'admission réelle reste le compute-pool. Les signatures CPU12 historiques sont des formats de reprise exacts et ne réintroduisent aucun plafond portable.

La même discipline vaut à la frontière géométrique Sparse SfM : les champs publics uint32_t max_iterations et minimum_inliers de relative pose et PnP doivent être représentables par le int OpenCV, donc être au plus INT_MAX (max_iterations reste strictement positif). Une valeur supérieure est refusée avant narrowing, allocation, appel solveur ou mutation des sorties. L'encodage F0 reste u32, et tous les paramètres FROZEN représentables, valeurs de référence, fingerprints et résultats scientifiques restent inchangés : cette borne est opérationnelle/ABI externe, pas une nouvelle politique scientifique.

Le Geometric Verifier prépare désormais des Match Results indépendants en parallèle, puis le propriétaire unique publie dans l'ordre et checkpointe un préfixe contigu. Sur le corpus représentatif réel de 4 113 parents, les sorties, IDs canoniques et le digest scientifique 9401ef6168804b6f1d51f4cdf64cd6b33cbebd2934e5294c8feacc87f9c8ce86 restent identiques. Les débits Task complets CPU1/2/4/8/12 sont 60,7514/83,9556/104,7545/116,6329/119,7606 parents/s. CPU12 n'ajoute que 2,68 % sur CPU8, sous le seuil d'acceptation 5 % : le plafond utile est 8, la fenêtre sûre et le lot maximal restent 16. La preuve S21 complète reste une preuve historique CPU1/batch8 valide : 172 741 parents traversés, 172 275 applicables, 24 065 vérifiés et 148 210 rejetés en 3 221,758 s, sans doublon, avec reprise/SIGKILL validée. Elle n'est pas présentée comme un rerun du nouveau chemin. Le manifest retenu de la preuve outer-parallel a le SHA-256 52a4412299c74050a66d5690122a793c9451c79faf47e32b6e65a5958f804856 : il prouve littéralement les 4 102 lignes applicables, leurs IDs 1..4102, statuts, modèles, masques et compteurs, pas seulement un wall. Un second run complet S21 de 3 221 s n'est pas requis pour accepter cette maintenance : le run S21 acquis porte déjà la science/persistance v3 gelée, tandis que la preuve réelle bornée et les tests de publication/reprise discriminent exactement le seul changement, l'ordonnancement externe de préparation.

Task, Queue, persistance et concurrence

Une seule Queue possède un worker et un callback actif. Le Governor admet la séquence ; la Task possède son état d'exécution ; la Queue possède seulement ordre/backpressure et, après retour du callback terminal, une histoire de 64 snapshots maximum. Task et userdata sont détruits hors mutex Queue et seulement après la fin du callback.

La fermeture Queue ferme d'abord l'ingress, attend le worker et tous les appels déjà enregistrés, puis détruit exactement une fois. Comme pour tout pointeur C brut, le propriétaire doit empêcher le début de nouveaux appels après le début de la destruction. Les callbacks terminaux peuvent observer les API read-only, mais ne doivent pas retirer synchroniquement leur propre entrée, détruire leur Queue ou attendre une opération dépendant de leur propre retour.

Les IDs Queue générés sont monotones et leur épuisement à UINT64_MAX est sticky ; ni un ID restauré plus petit, ni l'éviction de l'histoire ne réarme la génération. La reprise restaure état métier et estimation durable, jamais thread, callback, pointeur, réservation ou contrat actif. Les signatures de ressources historiques explicitement reconnues sont normalisées seulement en mémoire ; aucun checkpoint « estimate-only » n'est publié. Les curseurs métier et la publication scientifique restent les seules frontières de progression.

Ouvrir, fermer ou changer de projet constitue une frontière de session exacte. Les vues libèrent leurs borrows, puis la Queue est annulée, jointe et détruite avant Project DB ; une unique Queue vide est ensuite recréée et les observateurs sont rebondés. Les callbacks terminaux peuvent donc finir avec leur DB encore vivante, et histoire/namespace Queue d'un projet ne fuient pas dans le suivant.

Observatoire et centre de contrôle TUI

CURRENT / VALIDATED OPERATIONAL. ncurses, l'entrée et le rendu appartiennent uniquement au thread principal. Un modèle de vue pur reçoit des copies bornées et ne mute ni Queue, ni Governor, ni DB, ni science. L'observateur coalesce les captures ordinaires pendant au moins une seconde et couvre exactement le pire cas production : 64 pending, une active et 64 historiques, soit 129 Tasks. Les ABI historiques TaskSnapshot, ResourceSnapshot, AppState et layout sont préservées ; les identités typées, comptes durables, contrats installés, SwapTotal et vues riches passent par des types/fonctions additifs.

La progression exacte emploie toujours le préfixe métier durable quand il est connu. Un terminal 2/7 est une erreur d'intégrité et un terminal typé sans compteur scientifique reste indéterminé ; aucun des deux ne devient un faux 100 %. L'EWMA/ETA exclut le préfixe repris et exige deux intervalles positifs ; les états calculating, indeterminate, stalled, throttled et complete restent explicites. Le pipeline expose Acquisition, RAW, Quality, Features, Visual Index, Candidate, Matcher, GV, Tracks, Sparse SfM et Dense, avec NOT_READY/READY/QUEUED/RUNNING/THROTTLED/BLOCKED/COMPLETE/FAILED/NOT_APPLICABLE. Dense future reste NOT_APPLICABLE et n'est jamais montrée en cours.

Le panneau ressources présente CPU actif/admis/disponible et sa raison, GPU/mémoire/busy/backend, RAM/MemAvailable/réserve, swap total/utilisé/deltas, lot/inflight/helpers/I/O/scratch et Governor GREEN/YELLOW/RED. Le contrat de l'exacte Task active est prioritaire ; backend/inflight/helpers ou raison restent UNKNOWN lorsqu'une association Task+séquence n'est pas prouvée. L'espace scratch enregistré auprès du Governor est présenté séparément des détails physiques contrôleur et n'est jamais additionné à la RAM.

Les layouts validés sont full à partir de 100×30, compact à 72×20 et jusqu'au minimum 60×15 ; en dessous, seul Terminal trop petit est rendu. Vert/jaune/ rouge/cyan/bleu/magenta et dim/bold ont toujours des libellés de repli sans couleur. F10 SSD est un segment littéral garanti à 60 colonnes dans les modes idle, saisie et import. L'aide est contextuelle : saisie = Enter/Échap/F10, import actif = X/F10 avec q/Échap désactivés, et les écrans idle n'annoncent que les touches réellement traitées.

L'écran optique effectue seulement des lookup metadata exacts, accepte le Meike manuel sans EXIF comme cas normal, crée de nouveaux profils/configurations immuables et affecte explicitement groupe de campagne ou Capture. Il liste et sélectionne uniquement les calibrations exactement compatibles ; unresolved, absence, ambiguïté, incompatibilité, BUSY, I/O et corruption restent visibles. Les pages de 16 rapportent leur compte local et l'existence exacte d'une suite ; [ revient à la première page, ] avance et R retente explicitement.

Contrôleur SSD externe optionnel

Le contrôleur SSD est la frontière physique synchrone et bornée vers UDisks2 via GIO/GDBus. Il découvre exactement les labels LARDON_SWAP et LARDON_SCRATCH, exige des UUID stables et une même identité UDisks Drive, et ne dépend jamais d'un /dev/sdX, modèle, numéro de série ou object path comme identité produit. Un renommage de device node ne change donc pas l'identité.

Les états publics sont ABSENT, DETECTED, ENABLING, ENABLED, IN_USE, DRAINING, SAFE_TO_UNPLUG et ERROR. Les snapshots sont des copies bornées ; les valeurs inconnues restent explicitement inconnues. Le poll ordinaire est coalescé autour d'une seconde. Le chemin production n'appelle pas statvfs depuis le poll synchrone : un scratch monté peut donc rapporter total/libre inconnus.

La conversion vers le Governor valide l'état complet et échoue fermée. Toute paire ou autorité (pairing_valid, allocation, enable/disable/cancel, ou état ENABLING/ENABLED/IN_USE/DRAINING/SAFE_TO_UNPLUG) exige la détection actuelle du Drive et des deux partitions, les trois identités exactes non vides, des tailles de partition connues strictement positives et des faits mount/activité/ capacité cohérents. ABSENT rejette toute télémétrie active, montée, louée ou capacitaire ; un DETECTED partiel reste visible mais non actionnable. Un hazard ERROR déconnecté peut conserver son tuple pour la sécurité, jamais pour allouer ; can_disable n'est acceptable qu'après reconnexion prouvée de l'exact tuple original.

L'activation appelle directement UDisks et exige le point de montage exact /mnt/lardon-scratch. Elle ne formate, partitionne, répare, fsck, poweroff ni supprime rien. Avant toute action potentiellement side-effecting, l'ownership physique est lié conservativement au tuple Drive+deux UUID. Un timeout ou une vérification indéterminée laisse un hazard sticky ; aucun disque de remplacement ne reçoit alors Start/Mount/Stop/Unmount. Seul le même tuple reconnecté peut être vérifié et drainé jusqu'à SAFE_TO_UNPLUG.

Le scratch utilise au plus 64 leases explicites. L'adresse de l'objet lease fait partie de son ownership process-local : copie, reconstruction, objet étranger, stale ou double release ne décrémentent rien. DRAINING refuse de nouvelles leases. Le swap n'est arrêté que si les leases sont nulles, que les PSI et deltas swap sont calmes, et que son usage peut être absorbé tout en conservant la réserve dure de 3 Gio. La propriété UDisks Swapspace.Active doit être un booléen exact connu ; absence ou mauvais type échoue fermé. Aucun arrêt forcé n'existe.

Le contrôleur ne crée ni thread de polling, ni scheduler, ni capacité Task scratch implicite. L'adaptateur TUI possède au plus un thread joinable pendant un poll ou contrôle UDisks borné ; il ne touche jamais ncurses. F10 consomme exclusivement can_enable, can_disable ou can_cancel_drain du snapshot validé, jamais une inférence depuis l'état. Les huit états, identité stable, modèle, lien, swap, scratch, mount, usage, drain et raison sont présentés ; une valeur inconnue reste UNKNOWN, et SAFE_TO_UNPLUG est explicite.

Après chaque snapshot ou contrôle validé, l'adaptateur enregistre l'état physique auprès du Governor. Un snapshot malformé enregistre ERROR et bloque les nouvelles allocations. Les wrappers Governor sont l'unique voie de lease scratch de production ; le contrôleur conserve seulement son API physique basse. Les quatorze Task kinds actuels n'ont aucun consommateur scratch, donc zéro lease est la vérité courante et une capacité disponible n'est pas un usage. La génération source peut légalement saturer à UINT64_MAX. Une update publique matériellement différente au même watermark reste stale et ne peut pas rendre de l'autorité ; seule la fin sérialisée du wrapper exact déjà engagé peut réconcilier à ce watermark sa propre acquisition/libération et le compte de leases fondé sur les adresses. Même son erreur ne peut recopier un compte stale. Le shutdown suit Queue/leases → fermeture projet → join+unregister SSD → contrôleur → Governor. Tous les tests SSD/TUI utilisent un provider factice et n'exécutent aucune mutation réelle de l'hôte.

GPU, build et ABI

Le backend Vulkan ORB Matcher est une voie production validée et GPU-first en mode AUTO quand l'admission GPU/UMA est sûre. CPU reste le fallback portable. Le batch AUTO utile est 8 ; batch 12 et inflight 2 restent des bornes de sûreté et de benchmark rejetées pour la politique normale faute de gain supérieur à 5 %. Candidate, Feature et Visual Index restent CPU ; SIFT/RootSIFT Vulkan est rejeté par l'évidence disponible ; GV reste CPU et conserve son USAC interne non parallèle. Sur la Radeon 780M réelle RADV PHOENIX, la feasibility explicite compare 24 160 requêtes SIFT et 24 161 RootSIFT : chaque mode produit une divergence d'index, respectivement 20 251 et 20 824 divergences de bits de distance, mais zéro divergence de décision Lowe. C'est une preuve de feasibility décisionnelle, pas une équivalence binaire ; le hot path SIFT/RootSIFT Vulkan reste donc rejeté. Les overrides CPU/Vulkan sont des outils de debug, benchmark ou reproductibilité, pas un choix backend exigé de l'utilisateur normal.

Le dépôt est mixte C17/C++17. Les headers publics sous include/lardon3d/ restent compilables comme C17, n'exposent aucun type C++ et décrivent ownership, bounds et erreurs. Aucune exception C++ ne traverse une frontière C. Meson et Ninja sont le build de référence ; Clang est préféré et GCC reste couvert pour les contrôles C17 pertinents. GIO/GDBus est la dépendance directe du contrôleur SSD ; aucune commande shell mount, swapon ou équivalente n'est construite.

Évidence finale acquise et revue indépendante

Builds normaux, Vulkan et ABI

Deux répertoires entièrement frais utilisent Clang/Clang++ 22.1.8, C17 et C++17 :

  • portable -Dvulkan_orb=disabled : graphe complet 931/931 puis suite sérielle 64/64 ;
  • Vulkan -Dvulkan_orb=enabled : graphe complet 939/939 puis suite sérielle 65/65, dont orb-vulkan-backend sur l'iGPU réel AMD Radeon 780M Graphics (RADV PHOENIX) ; la dépendance Vulkan détectée vaut 1.4.357 ;
  • la feasibility SIFT/RootSIFT, cible non enregistrée dans la suite normale, a été compilée et exécutée explicitement ; ses divergences exactes sont consignées ci-dessus et n'autorisent aucun backend production nouveau ;
  • les probes autonomes stricts GCC/Clang C17 et C++17 passent 76/76 sur les 19 headers publics modifiés ou nouveaux, le fixture ABI historique, le lien de l'application et git diff --check passent, et git status --short -- scan3d reste vide.

La correction de narrowing Sparse SfM, postérieure à la capture des deux logs complets, a ensuite passé son test focalisé, 20 répétitions, ASan/UBSan ciblé, les inclusions C17/C++17 et le lien application. Elle retire les conversions publiques uint32_t → int sans changer les appels valides ni la science.

Déterminisme post-freeze du fixture Feature

La validation post-freeze a exposé une dépendance exclusivement test à l'activité de l'hôte : test-feature-task construisait des profils et Governors synthétiques, mais leur admission capturait encore le vrai /proc/loadavg, les PSI et les deltas de swap. Une charge minute suffisante pouvait donc maintenir correctement la Queue en WAIT jusqu'à son délai de 30 s, sans divergence scientifique ni défaut de la politique production.

Le fixture possède désormais un override privé par Governor qui copie sous mutex un ResourceSnapshot complet et ne rafraîchit que son horodatage. Il est compilé exclusivement dans test-feature-task ; la capture réelle et les symboles production restent inchangés. Une régression directe prouve que la charge synthétique 5 produit toujours WAIT, puis que la charge 0 produit START : le seam contrôle la télémétrie, jamais la politique. La revue bornée a ensuite identifié le second Governor créé par le helper runtime ; celui-ci reçoit maintenant le même snapshot avant toute création de Queue, avec cleanup explicite des échecs.

Après cette correction et sa revue, le test Feature complet passe 100/100, la matrice ordonnée Candidate→Matcher→Matcher pipeline→Feature 4/4, la suite portable finale 64/64 et la suite Vulkan finale 65/65. Les exécutions ciblées ASan/UBSan avec detect_leaks=0 et TSan passent également. La qualification LSan externe décrite ci-dessous reste inchangée. Une suite normale mixte lancée après une reconstruction large a aussi observé un unique timeout task ; le ciblé immédiat et la matrice de revue 100/100 passent. Ce cas reste qualifié non reproductible/environnemental : ni le timeout ni le comportement Task n'ont été modifiés.

ASan, UBSan et LeakSanitizer

Le build frais Clang 22.1.8 address,undefined, portable/Vulkan désactivé, a d'abord été exécuté avec détection de fuites : 57 OK, 6 FAIL, 1 TIMEOUT. Cette première passe n'est pas masquée :

  • feature-store, visual-index, candidate-pair-task, precision-features et precision-consolidation terminent chacun sur la même fuite externe, exactement 3 808 octets en 68 allocations, avec la dernière frame /opt/cuda/lib64/libOpenCL.so+0x2fd4, Build ID b3217362255db6f1188e7596454ffe8bc4606b53, résolue vers /opt/cuda/targets/x86_64-linux/lib/libOpenCL.so.1.0.0, sans frame projet ;
  • feature-task atteint une fois son attente adaptative interne de 30 s et task le timeout Meson de 30 s, sans diagnostic ASan/UBSan/LSan.

Après cette attribution, la matrice complète repasse avec detect_leaks=0 mais ASan et UBSan toujours actifs : 64/64, zéro timeout. Une matrice séparée de vingt cibles dont ldd prouve qu'elles ne chargent ni OpenCV ni OpenCL passe 20/20 avec LeakSanitizer actif. Les anomalies de temps initiales ne s'étaient pas reproduites dans le suivi initial : 20/20 exécutions feature-task à affinité complète et 100/100 exécutions task avec perturbations allocateur. Le cas Feature a été reproduit plus tard par la suite portable ordonnée, puis déterminisé comme décrit ci-dessus ; le cas Task reste non reproductible. La conclusion sanitizer exacte demeure : ASan/UBSan projet 64/64, chemins projet loader-free LSan 20/20, limitation LSan du loader OpenCL externe conservée explicitement — jamais un faux « LSan global 64/64 ».

Concurrence

Le build frais GCC/G++ 16.2.1 TSan, volontairement Vulkan-disabled, passe les quatorze cibles concurrentes Task/Project/Queue/Governor/SSD/TUI/Candidate/ Visual/Feature/Matcher/GV 14/14, puis 220/220 répétitions déterministes, soit 234/234 exécutions. tests/tsan-opencv.supp ne supprime que les races issues des objets externes non instrumentés libopencv_features.so, libopencv_core.so et libtbb.so; aucune frame Lardon3D n'est supprimée. Cette preuve ne couvre pas Vulkan sous TSan : le backend réel est couvert par le build/suite Vulkan ci-dessus et ses tests propres.

Audit des avertissements

Le build Vulkan complet et ses cibles non-default ont produit 785 émissions : 117 dans des sources projet/tests/benchmarks et 668 dans les headers OpenCV installés. L'audit n'a trouvé qu'une famille matérielle pour la frontière publique courante : le narrowing Sparse max_iterations/ minimum_inliers → int, corrigé et revalidé comme décrit plus haut. Les avertissements restants sont classés :

Classe Emplacements Qualification
Baseline production, non matériel pour cette maintenance src/acquisition_campaign_task.cpp:68, src/raw_development.cpp:185, src/acquisition_pairing.cpp:549, src/feature_extractor_opencv.cpp:231 conversions sur valeurs déjà bornées par leur structure/validation ; aucune identité ni sortie modifiée dans ce ticket
Baseline géométrique bornée src/sparse_sfm_geometry.cpp:312-313 index OpenCV signé sur les deux vecteurs construits avec le même nombre de colonnes ; distinct du narrowing public corrigé
Tests/benchmarks seulement tests/test_dense_mvs.cpp:45, tests/test_precision_consolidation.c:846-847, shadows dans tests/test_sparse_sfm_incremental.cpp:441-723, API d'initialisation OpenCV dépréciée et indices signés dans tests/benchmark_geometric_verifier.cpp:101-190 aucun chemin production ni format durable
GCC TSan, non matériel src/feature_task.c:209-215, src/sift_task.c:170-175 -Wmaybe-uninitialized interprocédural ; le callback fournit une Task non nulle et le helper initialise le contrôle avant toute autre sortie d'échec
Externe headers OpenCV 5 installés ; découverte CMake Ceres conversions/extensions et warnings de generator expressions hors sources Lardon3D

Les catégories baseline ne sont pas promues en dette scientifique cachée : elles sont consignées pour une maintenance ultérieure, tandis que le seul narrowing pouvant accepter une valeur publique non représentable a été traité avant la revue finale.

Revue finale et clôture de la gate

Les tests focalisés TUI/runtime/SSD, les répétitions déterministes et la validation F10 par providers factices restent acquis. La preuve GV finale comprend 8/8 tests d'intégration focalisés, 60/60 répétitions de stress, 3/3 ASan/UBSan et 3/3 TSan ; les détails Project DB v23 sont consignés plus haut.

L'unique revue finale indépendante GPT-5.6 SOL/ULTRA a conclu PASS, avec zéro finding bloquant. Elle a indépendamment exécuté le build portable, la suite complète 64/64, la matrice focalisée 15/15, les probes strictes GCC/Clang C17+C++17 76/76 sur 19 headers publics modifiés/nouveaux, le fixture ABI, les négatifs de symboles/strings des seams production, la vérification du SHA-256 du manifest GV retenu et git diff --check. Cette preuve indépendante ferme la gate sans effacer les qualifications sanitizer consignées ci-dessus. L'ordre autorisé est désormais :

science réelle acquise jusqu'à GV
→ GLOBAL_MAINTENANCE_AUDIT (PASS/FROZEN)
→ prochaine tranche séparée : poursuite réelle depuis Tracks
→ Sparse SfM réel / Dense selon calibration et roadmap

scan3d/ et les contrats scientifiques Tracks/SfM/Dense restent protégés et n'ont pas été modifiés par cette réconciliation.