Aller au contenu
Analyses

Présentation Hypostasia V3

PRESENTATION-V3.md Source
title #1 78231ee1

Hypostasia V3 — ce que devient le projet, et pourquoi

text #2 ffd173fa

Date : 9 août 2026, au soir (première rédaction le 8 août ; mis à jour après les couches corpus A-H et synthèse A-F). Lecteur visé : quelqu'un qui connaît Hypostasia mais pas les décisions de conception de ces dernières semaines — un contributeur qui arrive, un partenaire, ou le développeur qui reprendra ce code dans six mois.

text #3 52456687

Ce document explique et justifie. Il ne remplace ni les spécifications ( SPEC-synthese-carnet.md , SPEC-selection-des-preuves.md ), ni le plan de refonte ( PLAN/INSPIRATION_ATOMIC.md ), ni les maquettes ( tmp/maquettes/ ) : il dit d'où viennent les idées, comment les mécanismes fonctionnent, et ce qui sépare aujourd'hui l'intention du code.

text #4 2f9f1ca4

Une règle de rédaction, annoncée d'emblée parce qu'elle est le sujet même du document : quand un chiffre vient d'une étude menée sur d'autres systèmes et non d'une mesure sur Hypostasia, c'est dit explicitement. Un outil qui promet des synthèses vérifiables ne peut pas se présenter avec des chiffres invérifiables.

section_header #5 208537fb

1. Ce qui change, en une page

text #6 4101a941

Hypostasia V2 est un outil « un dossier contient des pages » : on importe un document, on l'analyse, des extractions apparaissent dans la marge, on les commente. La synthèse y était une version du document — synthetiser_page_task créait une nouvelle Page avec un parent_page et un numéro incrémenté, comme si résumer un débat revenait à le réécrire. Ce n'est plus vrai depuis le 9 août : la tâche a été réécrite, la synthèse est une note typée du carnet, et parent_page n'a plus aucun écrivain en production (§ 6.2).

text #7 471c3e7b

Hypostasia V3 en fait une plateforme de gestion de corpus qui produit des synthèses sourcées et contestables . Trois déplacements :

text #8 734e55ee

Le contenant change de nature. Le dossier devient un carnet , une note peut appartenir à plusieurs carnets, et les carnets se regroupent en bases de connaissances. La relation note↔carnet porte les catégories : chaque collectif classe avec son propre vocabulaire sans l'imposer aux autres. Modèle repris de Praxis (§ 5).

text #9 9c8bd495

La synthèse change de niveau et de statut. Elle cesse d'être une version d'une page pour devenir une note du carnet , en deux genres incompatibles dans un seul objet : le wiki , article thématique vivant mis à jour par opérations de section, et la synthèse dirigée , acte daté, figé, qu'un collectif adopte et auquel il peut se référer six mois plus tard. Mécanisme repris d'Atomic, corrigé sur plusieurs points (§ 4).

text #10 f04a772a

La citation devient un objet vérifié. Chaque affirmation d'une synthèse est reliée à ses preuves par un lien persisté, contrôlé mécaniquement (verbatim puis implication), affiché avec son état — et, plus important encore, la synthèse expose ce qu'elle n'a pas repris et ce que l'analyse n'a jamais lu . C'est la partie la plus travaillée de la refonte (§ 3), parce que l'état de l'art montre que c'est là que tous les systèmes comparables échouent.

text #11 6a68980e

Ce que ça débloque, concrètement : un compte rendu de conseil qui intéresse deux groupes de travail vit dans leurs deux carnets sans être dupliqué, avec un seul débat visible des deux ; douze séances étalées sur deux ans produisent un wiki qui se met à jour à mesure que le carnet grossit ; et une synthèse adoptée en assemblée peut être contestée dans l'outil — en remontant à la source exacte de chaque affirmation, ou en pointant l'argument qu'elle a laissé de côté.

text #12 3cd8456f

Le déclencheur n'est pas théorique. Deux demandes réelles — un lycée (comptes rendus de conseils de vie lycéenne, profs et élèves) et des réseaux régionaux de tiers-lieux (veille de financements) — portent d'abord sur la gestion de corpus, pas sur la synthèse. Le besoin commercial a précédé le besoin technique (note « Hypostasia V3 — couche corpus et refonte de la navigation », mémoire Atomic, août 2026).

section_header #13 93386ee0

2. Les trois couches

text #14 3d4ca627

La V3 s'organise en trois couches, chacune consommant les garanties de celle du dessous. L'ordre de construction suit l'ordre de dépendance, et les trois couches sont désormais codées à des degrés différents.

table #15 38647cfc
CoucheObjetÉtat
Ancrage par élémentoù, exactement, dans quel texteimplémentée et migrée,pas encore branchéeau parcours utilisateur
Corpus: base → carnet → notequi range quoi, avec quel vocabulaireimplémentée, migrée et testée, écrans compris
Synthèse au niveau du carnetce qu'on affirme, sur quelles preuvessocle et vérification codés (modèles, citations, calculs, gardes, verbatim + juge NLI) ; interface à faire
section_header #16 55a8a195

2.1 L'ancrage par élément — le socle

text #17 ca82703d

L'ancien moteur ancrait une extraction par deux offsets dans le texte plat de la page. Défaut structurel : « un décalage n'est pas une adresse, c'est une distance depuis un point qui bouge » — corriger une coquille en haut de page faussait toutes les ancres du dessous.

text #18 2aa5b15d

Le moteur ELEMENT ancre dans l' élément de document : un paragraphe, un titre, un item de liste, un tableau, un tour de parole, chacun identifié par un UUID qui ne change jamais ( ElementDocument , core/models.py:1609 ). Une extraction porte une ou plusieurs portions , chacune bornée à l'intérieur d'un élément ( AncrageExtraction , hypostasis_extractor/models.py:666 ). La table de liaison n'est pas un luxe : mesuré sur les données du projet, une extraction d'une seule phrase enjambe déjà deux éléments dans 7,5 % des cas, une extraction de deux phrases dans 76 % des cas.

text #19 81f0353c

Autour de ce modèle, une famille de services ( hypostasis_extractor/services/ ) :

text #20 9bd2c3e3

ancrage.py — traduit un span rendu par le LLM sur un chunk en portions par élément, par intersection ; les séparateurs de jonction n'appartiennent à aucune portion ;

text #21 41760db4

chunking.py — découpe sur les frontières d'éléments, jamais au milieu ;

text #22 d6170434

analyse_par_element.py — le pipeline chunks → LLM → ancres ;

text #23 f48212ac

reconciliation.py — repositionne les portions après une correction de texte, avec la règle « on ne devine pas » : un passage qui apparaît deux fois pourrait être l'un ou l'autre, on le marque DETACHEE et un humain tranche ;

text #24 352faa3c

moteur_structure.py — scission et fusion d'éléments sans perdre les ancres (le contenu total ne change jamais, seul le découpage change) ;

text #25 43dfe433

masquage.py et reingestion.py — écarter du bruit sans le supprimer, et ré-analyser un pad qui grossit en reconnaissant les éléments inchangés par empreinte (sur un pad de 200 comptes rendus, seuls ~2 % du contenu repartent en analyse) ;

text #26 ef8032f2

garde_edition.py — refuse l'édition pendant qu'une analyse tourne, pour ne jamais écrire d'ancres calculées sur un texte qui n'existe plus.

text #27 906e0688

Côté affichage, front/services/rendu_elements.py est écrit et testé : il remplace l'injection de balises par offsets par un découpage du texte de chaque élément en segments aux bornes des portions, ce qui rend enfin représentables les extractions en plusieurs portions et les chevauchements — 3 898 paires d'extractions se chevauchent en base, ce n'est pas un cas d'école. Mais aucun code de production ne l'appelle : ni vue, ni template, ni tâche (vérifié par recherche exhaustive le 9 août — il n'apparaît que dans sa propre suite de tests et dans les commentaires de la maquette). Comme le pipeline d'analyse par élément, il attend le branchement (§ 6.1).

section_header #28 1d08e987

2.2 Le corpus — base → carnet → note

text #29 f60380fd

Implémentée (voir § 5 pour le modèle, § 6.2 pour l'état exact). Deux relations N-N portées par des tables de liaison ( AppartenancePageDossier , AppartenanceDossierBase ) qui portent elles-mêmes les catégories, l'épinglage et l'ordre manuel — modèles, migrations, permissions, endpoints et écrans carnet / note / bases sont en place.

section_header #30 a418c811

2.3 La synthèse au niveau du carnet

text #31 00e9c977

Socle codé, interface à faire. La synthèse est devenue une note typée du carnet ( type_de_note : note / wiki / synthèse), avec un garde-fou mécanique : une synthèse n'est jamais source d'une autre synthèse — sans cette clause de filtre, le wiki finirait par se citer lui-même et la délibération d'origine disparaîtrait sous ses propres résumés. Le garde-fou est un champ, pas une propriété calculée, précisément pour être une clause filter() ; il est exercé par test et refusé à deux étages (la vue et, en profondeur, la tâche).

section_header #32 47046ee4

2.4 Pourquoi cet ordre

text #33 40878597

La synthèse cite des preuves ; une preuve est une portion ancrée ; une portion ancrée n'existe que si l'ancrage survit aux éditions, scissions et ré-ingestions. Construire la synthèse d'abord aurait produit des citations posées sur du sable — précisément le défaut que l'état de l'art constate partout ailleurs (§ 3). Et la couche corpus s'intercale parce que la synthèse a besoin d'un périmètre défini par des humains (le carnet et ses facettes), pas par une distance vectorielle (§ 4.3).

section_header #34 d998d7f7

3. La méthode de vérification des preuves

text #35 3c6a6d00

C'est le cœur de la V3, et la partie de ce document qui mérite la lecture la plus attentive. Elle repose sur une revue de l'état de l'art menée en août 2026 (note « Sourcing et attribution dans les systèmes RAG — état de l'art 2026 », mémoire Atomic), croisée avec la lecture du code source de plusieurs moteurs de synthèse réels. Tous les chiffres de cette section proviennent de benchmarks et d'études menés sur d'autres systèmes — ALCE, LongBench-Cite, CiteControl, FACTS notamment — et non de mesures sur Hypostasia. Ils dimensionnent le problème ; ils ne mesurent pas notre solution.

section_header #36 77934592

3.1 Quatre résultats qui dictent la conception

text #37 3aa746b9

1. Citation présente ≠ citation correcte. Sur les benchmarks d'attribution (ALCE, LongBench-Cite), les citations sont formellement présentes dans les sorties, mais seulement 39 à 77 % sont factuellement vérifiées — c'est-à-dire que la source citée soutient réellement l'affirmation. Le taux de citation est la métrique que la plupart des produits affichent, et c'est la moins informative. Conséquence directe : afficher des renvois [N] ne prouve rien ; il faut un étage qui vérifie , en aval de la génération.

text #38 36d98408

2. L'attribution s'effondre en multi-sources. Dès qu'une affirmation combine deux sources, l'attribution correcte tombe autour de 30 % . Or toute synthèse qui croise des documents — c'est-à-dire toute synthèse intéressante — vit dans ce régime. Conséquence : l'état de vérification doit être porté par paire (affirmation, source) , jamais par affirmation. Une phrase appuyée sur deux sources ne peut pas porter un verdict unique : l'une des deux peut être bonne et l'autre fausse, et c'est le cas le plus fréquent.

text #39 186c6f7d

3. Demander au modèle de citer plus finement dégrade l'attribution, de 16 à 276 %. C'est le résultat le plus contre-intuitif et le plus actionnable de la revue : contraindre le LLM à citer à la phrase plutôt qu'au paragraphe empire la qualité d'attribution, dans des proportions massives. La leçon de conception : le grain fin doit venir d'une résolution mécanique en aval, jamais d'une consigne au modèle. On laisse le LLM citer au grain du paragraphe — là où il est le moins mauvais — et on affine ensuite, par calcul.

code #40 7465c33c
C'est exactement ce que l'ancrage par élément permet. Une extraction citée
par la synthèse porte déjà ses portions : élément identifié par UUID,
bornes dans le texte de cet élément (`AncrageExtraction`). Remonter d'une
citation au passage exact — jusqu'au caractère, jusqu'au tour de parole
horodaté pour l'audio — est une jointure, pas une prouesse du modèle. Le
grain fin est *gratuit* parce qu'il a été payé une fois, à l'extraction,
et vérifié par les mécanismes de la couche d'ancrage.
text #41 fc1606c4

4. 80,6 % des affirmations invérifiables sont des erreurs d'attribution, pas des hallucinations. Le contenu est vrai ; il est simplement rattaché à la mauvaise source. C'est une bonne nouvelle : une erreur d'attribution est réparable par vérification automatique — on peut chercher la bonne source dans le périmètre —, alors qu'une hallucination ne l'est pas. Ce chiffre justifie l'arbitrage central de la V3 : investir dans un étage de vérification plutôt que dans un meilleur prompt.

section_header #42 e417738d

3.2 L'architecture en cinq étages

text #43 2a4b667f

L'état de l'art converge vers une architecture que la V3 adopte intégralement. Chaque étage existe pour neutraliser un des échecs mesurés ci-dessus.

table #44 6ba2479f
#ÉtageCe qu'il neutralise
1Indexerau grain de la preuvel'écart entre ce qu'on cite et ce qu'on peut montrer
2Sélectionner avant d'écrirele biais de sélection implicite du modèle
3Générer augrain du paragraphe, résolution mécanique en avalla dégradation de 16 à 276 % (résultat 3)
4Vérifier: verbatim puis NLIles 23 à 61 % de citations non soutenues (résultat 1)
5Sérialiser enW3C Web Annotation, sélecteurs redondantsla fragilité des ancres dans le temps
text #45 4b5ac52b

Étage 1 — indexer au grain de la preuve. L'unité indexée doit être ce qu'on veut pouvoir montrer à l'utilisateur, pas le chunk de récupération d'un moteur RAG. Chez Hypostasia, cette unité existe déjà : l'extraction et ses portions ancrées. Un moteur qui indexe des chunks de 800 tokens ne pourra jamais surligner la phrase exacte ; un moteur qui indexe des preuves le fait par construction.

text #46 e1c51305

Étage 2 — sélectionner les preuves avant d'écrire. Le modèle reçoit un ensemble de preuves déjà choisi ; il ne les choisit pas en rédigeant. Sinon la sélection est invisible, non reproductible, et impossible à contester. Comment choisir est le sujet de SPEC-selection-des-preuves.md , et le point où Hypostasia diverge frontalement de la pratique courante (§ 4.3) : le périmètre est défini par les facettes du carnet — un humain a rangé, un humain filtre —, le regroupement s'énumère au lieu de s'échantillonner, et une passe transverse cherche les oppositions que la géométrie ne peut pas voir.

text #47 4156bd69

Étage 3 — générer au grain du paragraphe, avec des citations résolues mécaniquement. Le modèle cite des sources numérotées ; le code résout. Détail qui compte : le numéro [N] n'est jamais stocké ( SPEC-synthese-carnet.md § 4.3). Il est attribué à l'affichage, par ordre d'apparition, à partir d'identifiants d'extraction persistés. Un index stocké est instable entre deux ingestions : une citation [3] mémorisée pointerait vers autre chose après réindexation — défaut observé dans le code d'un moteur réel, où la déduplication par index détruisait en outre l'information de quelle affirmation citait quoi.

text #48 8955cea9

Le lien persisté est SourceLink ( core/models.py:1439 ) : quelle extraction ( extraction_source ), quelle portion exacte ( ancrage_source ), dans quel article, dans quelle section, avec quel état de vérification. Il est écrit depuis le 9 août par indexer_les_citations() : le markdown est la vérité, les liens n'en sont que l'index, reconstruit à chaque enregistrement à partir des marqueurs [[ext:N]] . Le durcissement voulu par la spec est en place, sous une forme plus fine qu'un PROTECT global : un signal pre_delete refuse la suppression d'une extraction citée par une synthèse dirigée (l'acte daté ne perd pas ses preuves) et bascule à l'état SUPPRIMEE les citations d'un simple wiki (vivant, le tour suivant corrige). Jamais d'orphelinage silencieux dans un cas comme dans l'autre.

text #49 7bf0f886

Étage 4 — vérifier, en deux contrôles en cascade.

text #50 bcef9cbe

Verbatim : le texte cité existe-t-il littéralement dans la source ? Contrôle déterministe, sans modèle, coût nul.

text #51 af7c1ab2

Implication (NLI) : la source soutient-elle l'affirmation ? Un modèle léger de vérification (type MiniCheck dans l'état de l'art) suffirait, avec droit de veto.

text #52 ae65985a

Le choix du juge a été tranché par le propriétaire le 9 août ( SPEC-synthese-carnet.md § 14, question n°3) : ce sera le LLM déjà configuré , appelé en lot (une requête juge N paires) et à la demande — un geste explicite de vérification, jamais automatique à la production. Le calcul est celui d'un serveur de 8 Go partagé avec la production : de l'ordre de 0,01 à 0,05 € par synthèse en lot, contre ~1 Go de RAM permanente pour un modèle local de moindre qualité sur du français délibératif. L'interface du service reste prévue pour brancher un modèle local plus tard sans réécriture.

text #53 ac6b1984

L'ordre importe : le verbatim élimine à coût nul les cas où le modèle a déformé la citation ; le NLI, plus coûteux, ne traite que ce qui reste. C'est l'étage que le résultat 4 justifie : puisque 80,6 % des invérifiables sont des erreurs d'attribution réparables, un vérificateur automatique récupère l'essentiel de ce qu'un meilleur prompt ne récupérerait pas.

text #54 b9982c32

Étage 5 — sérialiser en W3C Web Annotation, avec des sélecteurs redondants. TextQuoteSelector (le texte exact, avec préfixe et suffixe), TextPositionSelector (les offsets), et FragmentSelector avec Media Fragments URI pour l'audio et la vidéo ( t=npt:847.2,863.5 ). La redondance n'est pas décorative : c'est ce qui permet de retrouver une ancre après édition du document — si les offsets ont glissé, la citation avec son contexte permet de la relocaliser, et c'est déjà la logique de reconciliation.py . Le format est un standard ouvert : une annotation Hypostasia doit rester lisible hors d'Hypostasia.

section_header #55 21ebb490

3.3 Les trois états affichés — et ce qu'ils n'établissent pas

text #56 7f02e59a

À l'écran, chaque affirmation porte un état : vérifié (verbatim et NLI passent), faible (le passage existe mais le soutien est incertain), non sourcé (rien dans le périmètre ne soutient l'affirmation). Trois états et non une avalanche de scores : c'est un choix d'interface assumé, motivé par le résultat inconfortable du § 3.6.

text #57 bc0736d1

Ce que ces états n'établissent pas doit être dit avec la même clarté, parce qu'en contexte de gouvernance, « vérifié » sera lu comme « validé par quelqu'un ». Il ne l'est pas. Trois conséquences, inscrites dans la spec ( SPEC-synthese-carnet.md § 7.2) :

text #58 a97e68a1

L'état porte son vérificateur : modèle, version, date, seuil. Un état sans provenance est un argument d'autorité automatisé.

text #59 e7c1be4e

L'état est contestable : on peut débattre d'une extraction, il faut pouvoir débattre d'un verdict. Un humain peut poser un état « contesté ».

text #60 c2efefd3

L'état est par paire (affirmation, source) — voir résultat 2. Une phrase à deux sources porte deux verdicts.

text #61 275896ec

Deux raffinements complètent le tableau. D'abord, le statut de débat remonte à la citation : une affirmation « vérifiée » peut reposer sur une extraction que trois commentaires contestent — le renvoi doit le montrer, sinon le vert ment par omission. Ensuite, la provenance « débat » : une affirmation peut venir d'un commentaire et non d'une extraction — « un avenant écrit a été chiffré à trois cents euros » vient d'un commentaire d'Amina, pas du rapport. Classer ce cas « faible » maquillerait une provenance parfaitement légitime ; SourceLink.commentaires_source existe pour la porter, et la spec demande un état ou un attribut propre : sourcé par le débat.

section_header #62 303d0f75

3.4 Ce qui n'a pas été repris — la différence d'ensembles

text #63 0bfe08cd

Une synthèse dit ce qu'elle retient ; elle ne dit jamais ce qu'elle a laissé de côté. Or c'est calculable sans aucun appel au modèle :

code #64 04a18825
écartées = extractions du périmètre − extractions citées
text #65 1075b2f3

( SPEC-synthese-carnet.md § 8 ; pour une synthèse dirigée, le périmètre est figé à la production, sans quoi la question « qu'a-t-on écarté en mars ? » recevrait une réponse recalculée aujourd'hui, c'est-à-dire une réécriture en douce.)

text #66 b9e3ff10

Le calcul est codé depuis le 9 août ( core/services/synthese.py ), et la relecture adverse l'a durci sur deux points qui méritent d'être dits. D'abord, le périmètre d'une dirigée est figé deux fois : les notes et les extractions effectivement proposées au modèle ( extractions_du_perimetre + un drapeau, migration core.0051 ) — sans ce second figeage, une ré-analyse postérieure réécrivait « ce que la synthèse n'a pas repris » avec des extractions que l'acte daté n'avait jamais vues. Ensuite, les 292 synthèses historiques n'ont pas ce périmètre : plutôt que d'annoncer « tout a été écarté », le service refuse explicitement de répondre pour elles. Une réponse fausse à charge aurait été pire que pas de réponse. (Un drapeau levé sur un périmètre vide, lui, veut dire « rien n'avait été proposé » : écartées = ∅, et non « tout ».)

text #67 a6e4ad56

C'est probablement la fonctionnalité la plus différenciante de la V3, pour deux raisons qui tiennent en une phrase chacune. Elle ne coûte rien : une différence d'ensembles est déterministe, rejouable, et son coût LLM est zéro. Personne d'autre ne l'offre : la revue d'état de l'art n'a trouvé aucun système comparable qui montre ses écartées — et c'est ce qui transforme une synthèse en objet contestable, donc utilisable dans une gouvernance : voici les arguments que la synthèse n'a pas repris .

text #68 2eae7884

Une règle absolue en découle : jamais une liste stockée, toujours un calcul. Dans la maquette, la liste écrite à la main était fausse sur trois articles sur quatre avant qu'on la calcule. Et une limite à afficher plutôt qu'à taire : cette différence n'audite que les citations — une extraction lue par le modèle et utilisée sans être citée la traverse. C'est une borne inférieure de contrôle, pas une garantie.

section_header #69 289ec3bc

3.5 La couverture — « inventé » n'est pas « jamais extrait »

text #70 929a6296

« Non sourcé » confond aujourd'hui deux situations très différentes : le modèle a inventé — rien dans le document ne le dit — ou le modèle a raison, mais le passage n'a jamais produit d'extraction . Le cas est réel dans l'étalon : la synthèse du 12 mars affirme « l'ordre du jour sera transmis par écrit » , marquée non sourcée — alors que Jonas le dit littéralement au dernier tour de parole. Le passage existe ; l'analyse ne l'a simplement jamais extrait. Sans correctif, la chaîne d'audit présente comme une invention potentielle ce qui est un trou de couverture — un faux positif qui décrédibilise l'outil là où il prétend être rigoureux.

text #71 5c513786

La couverture répond : quels éléments du document portent au moins une extraction, lesquels n'en portent aucune. C'est une jointure ( portions_d_extractions , hypostasis_extractor/models.py:706 ), pas une estimation — aucun appel au modèle. Le calcul est codé (phase D, même service que les écartées) ; l'écran, lui, reste à faire. À l'écran il montrera un liseré élément par élément et un chiffre global — « 14 éléments sur 41 portent une extraction » . Avant d'adopter une synthèse, savoir que l'analyse a lu un tiers du document ou sa totalité change tout.

text #72 759d9690

Deux honnêtetés à afficher avec le chiffre. La jointure ne compte ni les extractions masquées, ni les ancres détachées, ni les jobs inachevés — sans quoi la couverture se gonflerait de ce qui n'est plus une preuve. Et le dénominateur inclut les éléments non textuels (titres, tableaux) qui ne porteront jamais d'extraction : ce rapport n'est pas un pourcentage de lecture comparable d'un document à l'autre.

text #73 af04d04c

Avec les écartées, la chaîne d'audit est complète : la couverture dit ce qui n'est jamais entré dans le périmètre ; l'écart dit ce que la synthèse a laissé parmi ce qui y était.

section_header #74 ccb40cb2

3.6 Le résultat inconfortable : r = −0,96

text #75 15d85171

La revue d'état de l'art rapporte une corrélation de −0,96 entre la précision des citations et l'utilité perçue par les utilisateurs (mesurée sur d'autres systèmes, pas sur Hypostasia). Autrement dit : plus un système est rigoureux sur ses sources, moins les gens l'aiment. Un texte constellé de renvois, de réserves et d'états de vérification se lit mal ; un texte fluide et affirmatif inspire confiance — à tort.

text #76 8bceccda

On ne cache pas ce résultat, on en fait un arbitrage explicite plutôt qu'un arbitrage subi :

text #77 e5b1806a

trois états visuellement discrets, pas un score par phrase ni une avalanche de badges — la rigueur est disponible au clic (le panneau de preuve montre la citation exacte, le débat attaché, le lien vers la source), pas imposée à la lecture ;

text #78 ed70e287

les écartées et la couverture vivent dans des panneaux dépliables, pas dans le corps de l'article ;

text #79 f8bbb0b0

et le public visé n'est pas celui des benchmarks : un collectif qui adopte une synthèse en assemblée a un rapport à la vérifiabilité qu'un utilisateur de chatbot n'a pas. C'est un pari, il est assumé, et il est dit ici pour que le lecteur de 2027 sache que le choix a été fait en connaissance de cause.

section_header #80 f6d81f65

4. L'inspiration Atomic

section_header #81 1b641abe

4.1 Ce qu'est Atomic

text #82 41edca5c

Atomic est une base de connaissances personnelle open source (MIT, Rust + React), local-first — « your notes, your hardware, your model ». Ses notes markdown (« atoms ») forment un graphe sémantique, et sa fonctionnalité phare est la synthèse wiki : un article vivant par sujet, généré avec citations inline à partir de tous les atoms qui le touchent, mis à jour quand la base grossit. Le code d'Atomic (v1.43.0) a été lu intégralement lors de l'analyse comparative d'août 2026 (note « Trois moteurs RAG open source décortiqués », mémoire Atomic) ; PLAN/INSPIRATION_ATOMIC.md (2 100 lignes) en avait déjà tiré une spec de refonte en avril.

section_header #83 57f3fada

4.2 Ce qu'on lui prend

text #84 82acadab

Le wiki thématique et vivant. Un article par sujet, alimenté par les extractions du carnet, qui se suit plutôt qu'il ne s'adopte. C'est le premier des deux genres de synthèse de la V3 — l'autre, la synthèse dirigée figée, n'a pas d'équivalent chez Atomic et vient des besoins de gouvernance d'Hypostasia.

text #85 e22ab98d

La mise à jour par opérations de section, pas par réécriture. Quand le carnet grossit, le modèle ne régénère pas l'article : il renvoie une liste d'opérations structurées — NoChange , AppendToSection , ReplaceSection , InsertSection — qu'un applieur fusionne ( PLAN/INSPIRATION_ATOMIC.md § 7). Bénéfices : les sections non touchées restent intactes au caractère près, le coût LLM se limite au diff, et chaque tour est inspectable.

text #86 0d811777

L'applieur existe ( core/services/section_ops.py , 9 août) : fonction pure, schéma d'opérations contractualisé dans la spec, rejet par opération — une hallucination ne jette pas les faits des autres opérations —, et un contrôle optimiste de concurrence qui refuse visiblement une proposition périmée plutôt que d'écraser un article qui a bougé entre-temps. Les sources d'une opération ne sont pas un champ parallèle déclaré par le modèle : elles sont dérivées des marqueurs présents dans le contenu, parce que le markdown est la vérité. Ce qui manque est l'étage au-dessus : le prompt qui produit ces opérations et l'écran qui montre le diff (phases G à I).

text #87 7a175cd7

Le cycle proposition → diff → acceptation. La synthèse n'écrase jamais un contenu existant : elle propose un diff qu'un humain accepte. Avec une exigence ajoutée par la spec : le diff d'un ReplaceSection doit montrer l' avant — approuver une réécriture sans voir ce qui disparaît n'est pas une acceptation.

text #88 0a4fd3f2

Le rejet mécanique des propositions dont le titre de section n'existe pas. Un heading absent de l'article ne peut pas être une approximation de placement : c'est une hallucination du modèle. La règle « titre introuvable = proposition rejetée » est un garde-fou déterministe, sans heuristique et sans appel au modèle. La V3 le reprend et l'étend : une opération dont les sources sont vides, ou dont une source n'appartient pas au périmètre, est rejetée de la même façon ( SPEC-synthese-carnet.md § 6.3).

section_header #89 99bd7f59

4.3 Ce qu'on corrige

text #90 2d7b0497

La lecture du code d'Atomic a autant appris par ses défauts que par ses qualités. Trois corrections explicites :

text #91 5e647529

logger.warning , contenu jeté en silence after_heading introuvable → fallback en fin d'article [N] stockée dans le markdown | jamais stockée : identifiants persistés, numéros attribués à l'affichage |

table #92 9197a89b
Comportement d'Atomic (et du § 7 initial)Correction V3
table #93 bed8c3ac
rejetvisible, contenu conservé et montré
table #94 1808ce4c
rejet visible — insérer à un endroit que le modèle n'a pas choisi est pire qu'un rejet, parce que personne ne le voit
text #95 b152315d

Ces trois corrections sont codées et testées. La relecture adverse en a ajouté deux que personne n'avait vues, et qui relèvent de la même famille : le contenu d'une opération pouvait contenir une ligne de titre et fabriquer ainsi une section que l'humain n'avait pas approuvée — au titre dupliqué, donc ambiguë pour toujours ; et une insertion sous un titre déjà présent produisait la même corruption. Les deux sont désormais rejetées. Seule l'opération d'insertion crée une section.

text #96 d6c9a187

S'y ajoutent les défauts relevés dans le moteur de citations d'Atomic, qui ont directement façonné l'architecture du § 3 : grain limité au chunk (impossible de remonter à la phrase), index instable entre deux ingestions, citations non résolvables silencieusement supprimées, clé étrangère désactivée (les citations survivent à la suppression de leur source), et zéro vérification que le chunk cité soutient l'affirmation. Chaque étage de la V3 répond à l'un de ces points.

section_header #97 c85a97c8

4.4 Ce qu'on refuse : la sélection par centroïde

text #98 32e4bc34

Atomic sélectionne les preuves d'un wiki par proximité au centroïde sémantique : le point moyen des contenus d'un sujet, puis les K plus proches. Pour une base de connaissances personnelle, c'est un bon choix — on veut l'idée dominante. Pour une synthèse délibérative, c'est structurellement destructeur : une position minoritaire est, par construction géométrique, éloignée de la moyenne. Elle ne remonte jamais. Ce n'est pas un défaut de réglage — c'est ce que l'algorithme calcule ; augmenter K ne change pas l'ordre, on descend juste plus bas dans une liste triée par conformité.

text #99 0b1b18be

Le cas est monté dans l'étalon : une extraction dit que « la renégociation du bail repose sur un accord verbal avec le propriétaire actuel » . Personne d'autre n'en parle — donc elle est loin du centre — alors qu'un membre du conseil la commente : « c'est le vrai risque du budget » . Un tri par centralité l'écarte ; une synthèse budgétaire sans elle est fausse.

text #100 ef9ffe5d

D'où la règle de SPEC-selection-des-preuves.md , volontairement plus sévère qu'une version intermédiaire :

text #101 627f5cf9

Le centroïde n'est ni nécessaire pour former un groupe, ni admissible pour

text #102 51360716

le représenter. Il ne sert qu'à décrire un groupe déjà formé.

text #103 2c68ab9c

« Ni nécessaire pour former » : l'algorithme retenu — agglomératif à liaison simple, avec seuil de distance — compare des paires de membres, jamais un centre ; il accepte les singletons (une position isolée reste un groupe visible, elle n'est pas absorbée), ne demande ni k ni graine (donc reproductible, donc auditable), et chaque appartenance s'explique par une arête : « entrée parce qu'à distance 15 d'un membre, sous le seuil de 24 » — une explication qu'un humain peut contester. Il a son propre défaut, et il est sérieux — voir § 4.5.

text #104 11a8e232

« Ni admissible pour représenter » : un groupe s'énumère, il ne s'échantillonne pas. Toutes les extractions du groupe partent au modèle ; le groupe structure le contexte, il ne le réduit jamais. Chiffré sur l'étalon : résumer chaque groupe par son membre le plus central efface 11 extractions sur 19 — et dans le pire cas, le représentant n'est ni l'une ni l'autre des deux faces d'un désaccord : les deux disparaissent, sans laisser trace qu'il y a eu débat.

text #105 d467daa6

Deux compléments que la géométrie ne peut pas fournir. Les embeddings mesurent le sujet , pas la position : deux avis opposés sur la même question sont proches. Une passe transverse (LLM, sur tout le périmètre, jamais groupe par groupe) produit la liste des oppositions — y compris celles dont les deux faces n'ont aucun mot en commun. Et le tri par le débat exploite le signal qu'aucun autre outil ne possède : une extraction portant trois commentaires contradictoires est littéralement le contraire d'un consensus ; le centroïde la rejetterait, Hypostasia la fait remonter en premier. Ce signal n'a rien coûté à produire : c'est la délibération elle-même.

section_header #106 78e99a04

4.5 Le défaut de la méthode retenue, et pourquoi on le montre

text #107 b8a7f487

Un document qui n'exposerait que les défauts des méthodes écartées serait malhonnête. La liaison simple a le sien, et il est sérieux : l'effet de chaîne . A touche B, B touche C, et A se retrouve groupé avec C sans l'avoir jamais approché. Un seul point intermédiaire peut fusionner deux sujets sans rapport.

text #108 d1753768

L'étalon le monte délibérément : une extraction qui parle du seuil financier et des statuts relie deux grappes distantes. Au-delà d'un certain seuil, elles n'en font plus qu'une, et le groupe résultant mélange « comment décider » et « le cadre statutaire ».

text #109 363ead9c

Ce n'est pas masqué, c'est mesuré et affiché . Pour chaque groupe d'au moins trois membres, l'écran indique son diamètre — la plus grande distance entre deux de ses membres — et la part de ses paires au-delà du seuil : ces membres-là ne se sont jamais approchés, ils sont ensemble par un chemin. Quand cette part dépasse un tiers, un avertissement le nomme.

text #110 20efdb18

Un détail de conception mérite d'être raconté, parce qu'il illustre le genre d'erreur que ce dispositif doit attraper : la première version de la détection alertait quand le diamètre dépassait 2,2 fois le seuil. En montant le seuil, l'alerte disparaissait — alors que la fusion s'aggravait. Un critère proportionnel au réglage masque le problème à mesure qu'il grandit. La mesure retenue — la part des paires hors seuil — ne dépend d'aucun facteur arbitraire.

text #111 b0d0017e

Ce que l'étalon établit, et ce qu'il n'établit pas. Il établit que résumer un groupe par son membre central est destructeur : c'est chiffré, 11 extractions effacées sur 19, et dans certains cas les deux faces d'un désaccord — le représentant n'étant ni l'une ni l'autre, il ne reste aucune trace qu'il y a eu débat. Il n'établit pas que la formation des groupes soit sans danger : elle a le sien, et il faut le regarder.

section_header #112 03de6082

5. L'inspiration Praxis

section_header #113 af20d751

5.1 Ce qu'est Praxis

text #114 8e4b0329

Praxis (« En commun ») est une plateforme québécoise de mise en commun des savoirs, éditée par Projet collectif, un OBNL. Analyse menée en août 2026 sur la documentation et des corpus réels en ligne — pas de code source public (note « Praxis — plateforme de gestion de corpus à trois niveaux », mémoire Atomic). Ordres de grandeur observés : 944 carnets, des corpus de plusieurs centaines de notes, 4 855 notes « organisation » géolocalisées. Le modèle : note (unité de connaissance) → carnet (collection de notes) → base de connaissances (ensemble de carnets), avec deux relations N-N — une note vit dans plusieurs carnets, un carnet dans plusieurs bases — et un niveau base facultatif. Ce ne sont pas trois étages d'un arbre : c'est un graphe à trois types de nœuds.

section_header #115 ec19b95f

5.2 La catégorie portée par la relation — le point qui ne se devine pas

text #116 869a2bce

C'est le détail du modèle Praxis qui change tout, et le seul qui compte vraiment : la catégorisation d'une note est contextuelle au carnet. La catégorie n'est pas un attribut de la note ; c'est un attribut de la relation note↔carnet.

text #117 7eefb8b1

Exemple concret, transposé dans les maquettes : le compte rendu du conseil du 12 mars appartient au carnet « Conseil d'administration » et au carnet « Veille financement ». Dans le premier, il est classé « Budget » ; dans le second, « Subvention » . Même note, deux classements — et aucun des deux n'est « le bon » : chacun répond à la question de son carnet. (Chez Praxis, le cas documenté est la note « Appel à projets APCHQ 2026 », catégorisée différemment dans ses deux carnets.)

text #118 e2cf3e50

La conséquence est politique avant d'être technique : chaque collectif garde son vocabulaire de classement sans imposer le sien aux autres. Si les catégories étaient portées par la note, le premier collectif à classer imposerait sa taxonomie à tous les suivants — exactement ce qu'un outil de communs ne doit pas faire. Techniquement, cela se traduit par des tables de liaison qui portent les catégories ( AppartenancePageDossier , AppartenanceDossierBase ), avec une validation applicative : une catégorie appliquée doit appartenir au carnet de l'appartenance. Elle est codée à deux étages — le formulaire, pour une erreur lisible, et un signal, pour qu'un .add() lancé depuis un shell soit refusé lui aussi. Sans ce filet, le vocabulaire d'un carnet fuirait dans un autre, c'est-à-dire exactement ce que la catégorie portée par la relation existe pour empêcher.

text #119 3d0cb842

Les catégories sont elles-mêmes groupées en axes (chez Praxis : jusqu'à trois listes de dix catégories par carnet) — classer par thème et par échéance et par territoire sont trois questions indépendantes, pas une seule liste plate. Ces axes sont ce qui donnera plus tard son périmètre à la synthèse dirigée : on synthétise par axe , pas « les cinquante notes les plus centrales ».

section_header #120 0b96f48d

5.3 Ce qu'on prend d'autre

text #121 0b1150b0

L'épinglage et l'ordre manuel , pour donner à un carnet un ordre narratif — les temps d'une délibération — et pas seulement chronologique. Praxis s'en sert pour des actes de symposiums.

text #122 d2b5c10a

Le guide de rédaction par carnet , affiché au contributeur au moment où il contribue , pas rangé dans une page d'aide. Praxis assume que produire un bon corpus est un travail coûteux, et l'outille.

text #123 eb637cd2

Reporté, mais noté : citation APA et licence par note, flux RSS à quatre granularités, rôles fins gestionnaire/contributeur.

section_header #124 f25a577f

5.4 Ce qu'on refuse : la conversation déportée

text #125 a178a260

Praxis n'a aucune fonction de commentaire, nulle part — ni sur les notes, ni sur les carnets, ni sur les termes du lexique. La conversation est déportée sur Passerelles, la plateforme de discussion du même éditeur. C'est cohérent pour une base de savoirs stabilisés : un fil de discussion sous chaque fiche abîmerait les deux.

text #126 2505dce7

Hypostasia refuse ce choix, et ce refus est sa différenciation : le commentaire sur une extraction est la délibération. L'outil n'archive pas un savoir stabilisé, il fait travailler un désaccord. Les extractions et les commentaires appartiennent à la note, pas au carnet : une note dans deux carnets a un seul débat, visible des deux — le filtrer par carnet fragmenterait la délibération selon qui range quoi où. Conséquence à dire honnêtement à l'utilisateur : ranger une note dans un carnet public n'expose pas que le texte, mais aussi les analyses et les commentaires avec le nom de leurs auteurs — dans le cas du lycée, des échanges d'élèves. L'avertissement doit être chiffré et nommer ce qui sort.

section_header #127 4e989128

5.5 La conséquence d'interface : le N-N détruit l'arbre

text #128 e5fd5725

Découverte structurante de la spec corpus : la navigation actuelle est un arbre ( arbre_dossiers.html ), et un arbre suppose un parent unique . Sous le N-N, la même note apparaîtrait sous deux carnets — question sans réponse à l'écran : est-ce la même note ou deux copies ? Si j'en supprime une, l'autre disparaît-elle ? Praxis, significativement, n'a pas d'arbre.

text #129 4f671964

Principe retenu : l'overlay de navigation ne montre plus jamais de notes, seulement des contenants (bases et carnets). Le clic sur un carnet charge ses notes dans la colonne de lecture ; on est toujours dans un carnet, un seul à la fois — la question du parent ne se pose jamais. Le contexte perdu est rendu par un fil d'Ariane à bascule : Base › Carnet ▾ › Note , où le chevron ouvre la liste des carnets contenant la note et recharge la même note dans un autre contexte. Et la multi-appartenance devient visible là où elle a du sens : une bande « Dans N carnets » sur la note, montrant ses catégories dans chacun — c'est ce bloc qui rend le modèle compréhensible sans l'expliquer, on voit que les étiquettes diffèrent d'une ligne à l'autre.

text #130 6c6f2029

Où en est ce principe. Le bloc « Dans N carnets » existe et il est éditable (phase G) : chaque ligne montre un carnet, les catégories de la note dans ce carnet-là , et son nom rouvre la note dans ce contexte — la bascule est donc déjà là, sous une autre forme. Le reste ne l'est pas : l'arbre de navigation existe toujours et montre toujours des notes, et le fil d'Ariane à bascule Base › Carnet ▾ › Note n'est pas construit. Les écrans neufs ( /carnets/ , /bases/ ) cohabitent aujourd'hui avec l'ancienne navigation au lieu de la remplacer. C'est un chantier d'interface identifié, pas un principe abandonné.

section_header #131 cd5a3cc8

6. Ce qui est déjà là, ce qui reste à faire

text #132 aae0aa6b

État des lieux factuel, chaque affirmation vérifiée sur le dépôt le 9 août 2026 au soir. Les migrations appliquées sur la base de développement vont jusqu'à core.0051 .

section_header #133 7caf00a3

6.1 Implémenté et migré : le moteur d'ancrage par élément

text #134 467e670a

Les services listés au § 2.1 existent et sont testés (par exemple 19 tests à LLM simulé plus 3 à appels réels pour analyse_par_element.py , 24 pour garde_edition.py — CHANGELOG.md , entrées du 5 août 2026). Les migrations de schéma sont en place ( core 0035 à 0039, hypostasis_extractor 0031 à 0033).

text #135 29d8b7b7

La bascule des données existantes est faite : la commande manage.py basculer_vers_le_moteur_element ( core/management/commands/basculer_vers_le_moteur_element.py ) a été exécutée sur la base de développement — 538 pages, 29 921 extractions, 13 174 éléments créés. Trois versions successives de la méthode de ré-ancrage ont été comparées ; la version retenue traduit les anciens offsets par intersection plutôt que de rechercher le texte.

text #136 c4c28708

Deux mesures, deux périmètres — les deux comptent. Sur l'échantillon de contrôle des extractions commentées, celles qui portent un débat et qu'il fallait absolument préserver, le ré-ancrage atteint 99,7 % (113 sur 115), contre 39,7 % pour la recherche de texte initiale. Sur l' ensemble des 29 921 extractions, la première version de la commande n'en récupérait que 28,7 % (8 585 ancres) ; le CHANGELOG.md conserve ce chiffre en tête de son entrée du 5 août sans le mettre à jour après le passage à la traduction d'offsets. Le taux global après la version finale n'est donc pas documenté — à remesurer avant de le citer. Ce qui est établi : aucune extraction ni aucun commentaire n'a été supprimé, et ce qu'on ne retrouve pas reste en base, détaché, pour qu'un humain tranche.

text #137 1039b315

Nuance à connaître, et elle est importante : le pipeline d'analyse du moteur ELEMENT n'est toujours appelé par aucune tâche Celery ni aucune vue, et le service de rendu par éléments ( front/services/rendu_elements.py ) n'est appelé par rien non plus — l'un comme l'autre n'existent que pour leurs propres tests (vérifié par recherche exhaustive le 9 août). Il manque la tâche, le bouton, le compteur de tokens, et le branchement de l'affichage. Le moteur est prêt ; il n'est pas branché au parcours utilisateur, et les phases H à K de la spec d'ancrage — celles qui le brancheraient — ne sont pas commencées.

text #138 ef022d8a

Conséquence directe à connaître avant de lire le § 3 : le seul chemin d'édition de texte réellement exposé aux utilisateurs aujourd'hui reste celui de l'ancien moteur ( editer_bloc ), et il n'est pas gardé. Le propriétaire l'a tranché le 9 août ( SPEC-synthese-carnet.md § 14, question n°5) : au plus simple, on ne garde pas l'ancien chemin, et le gel effectif des sources citées sera livré avec le branchement du moteur élément. D'ici là, un passage cité par une synthèse adoptée reste techniquement modifiable par l'ancien chemin. C'est assumé, et c'est dit.

section_header #139 87ba18fb

6.2 Codé : la couche corpus, et le socle de la couche synthèse

text #140 61902b6d

La couche corpus est faite. Les huit phases A à H ont été livrées les 8 et 9 août, avec leurs relectures adverses et les trous de spec bouchés :

text #141 e70b3493

les six modèles ( BaseDeConnaissances , AppartenancePageDossier , AppartenanceDossierBase , ListeDeCategories , CategorieDossier , CategorieBase ), le champ role_special qui remplace le repérage des carnets « magiques » par leur nom, et les migrations core.0040 à 0045 (dont deux migrations de données réversibles : 537 pages avec dossier → 537 appartenances, cohérence contrôlée) ;

list_item #142 52b8c594
  • la validation des catégories par la relation, à deux étages ;
text #143 550789ed

les permissions dérivées des carnets — l'accès prend le carnet le plus permissif, la propriété s'élargit à l'owner d'un carnet contenant la note — et la bascule en un seul lot de tous les lecteurs de Page.dossier vers la table de liaison ; une conversion progressive aurait laissé une note ajoutée à un second carnet invisible dans ce carnet ;

text #144 977ae122

les endpoints (carnet, rangement, catégorisation, épinglage, ordre manuel, bases) et les écrans : carnet avec facettes, note avec son bloc « Dans N carnets », liste et détail des bases ;

text #145 4a27ef73

de l'ordre de 150 tests dédiés à cette couche, dont cinq scénarios de bout en bout dans un vrai navigateur, et les 37 contrôles de l'étalon qui passent.

text #146 be0c7ffc

Ce qui reste sur cette couche : la phase I (reprise du drag-drop et des derniers écrans multi-carnets) et la migration 3 , le retrait de Page.dossier . La clé étrangère est toujours là, écrite par un seul service, et elle porte le « premier carnet » pendant la coexistence — son retrait attend la recette.

text #147 45a046bf

La couche synthèse est codée pour tout son socle hors interface , phases A à F, chacune relue de façon adverse :

text #148 658ef82c

type_de_note et le garde-fou « une synthèse n'est jamais source » (A) ;

text #149 31136a33

SourceLink complété et l'indexeur de citations (B, ci-dessous) ;

text #150 6c75a267

les deux genres au modèle — Wiki (périmètre par catégories, recalculé à chaque appel) et SyntheseDirigee (périmètre de notes figé) —, migration core.0049 , plus core.0050 qui a estampillé les 292 synthèses historiques d'un enregistrement de synthèse dirigée (C) ;

list_item #151 2c5c7c6e
  • les écartées et la couverture (D, § 3.4 et § 3.5) ;
list_item #152 f5cd0896
  • la garde d'édition des sources citées (E, § 5 de la spec) ;
list_item #153 c38dd00c
  • l'applieur d'opérations de section (F, § 4.2 ci-dessus).
text #154 b26cd50b

Le point le plus visible pour un utilisateur : synthetiser_page_task a été réécrite. La synthèse n'est plus une version du document mais une note typée du carnet ; parent_page n'a plus aucun écrivain en production, ce qui rend le versionnage de page dormant — le schéma reste, plus personne ne l'alimente. La tâche exige du modèle des marqueurs [[ext:N]] et une ligne de contrôle finale : sans elle, la génération est tenue pour tronquée et l'échec est bruyant, rien n'est enregistré. Une synthèse tronquée passée pour un succès serait pire qu'une erreur. Le HTML dérivé passe par une allowlist stricte (un [texte](javascript:…) traversait l'échappement et devenait un lien actif), et la page, ses liens de citation, ses appartenances et son acte daté naissent dans une seule transaction.

text #155 08b22caa

Ce qui n'est pas fait sur cette couche : la vérification elle-même (phase G — verbatim, NLI en lot, les trois états, la provenance « débat »), en cours d'implémentation au moment où ces lignes sont écrites ; et toute l'interface — onglets Wikis et Synthèses du carnet, article, panneau de preuve (H), diff des opérations avec l'avant (I). Une synthèse produite aujourd'hui apparaît dans l'arbre latéral mais pas dans l'écran carnet, qui ne liste que les notes ordinaires ; c'est un séquencement assumé, l'inverse l'aurait rendue invisible partout.

text #156 f8a8a004

Enfin, SPEC-selection-des-preuves.md (§ 4.3 à 4.5 de ce document) n'est pas entamée : ni regroupement, ni passe transverse, ni tri par le débat, ni RegroupementRun , ni Opposition . C'est le prochain gros bloc après la vérification.

text #157 444b45fa

Un chantier décidé, pas encore ouvert : la bascule CSS. Le propriétaire a tranché le 9 août — l'ancien CSS est à jeter, le design de la maquette fait foi, et la bascule se fait à fonctionnalité constante . Elle est positionnée juste avant la phase H de la spec synthèse, pour une raison de coût : cette phase bâtit trois écrans neufs qui n'existent que dans l'étalon, et les écrire sur l'ancien CSS reviendrait à les écrire deux fois. Le cahier des charges est dans PLAN/bascule-css-cahier-des-charges.md ; il est explicitement un document de travail à arbitrer, rien n'est engagé.

text #158 4e57bf46

6.3 SourceLink : le schéma a trouvé son écrivain

text #159 dcd674c0

SourceLink existait en base depuis les migrations core.0026 puis core.0036 (qui lui avait ajouté ancrage_source ), mais aucun code de production ne l'écrivait ni ne le lisait — c'était le blocage central relevé par la première version de ce document. Il est levé depuis le 9 août.

text #160 49fd16e6

La migration core.0048 lui a ajouté ce qui manquait : la section et le rang dans la section, l'état de vérification par paire (affirmation, source) — la leçon du résultat 2 du § 3.1 —, l'état de la source (présente, supprimée, détachée) et le type de lien « cite ». indexer_les_citations() l'écrit, la tâche de synthèse l'appelle avec un périmètre désormais obligatoire dans sa signature, et un marqueur inexistant ou hors périmètre est retiré du texte et signalé : jamais gardé en silence, jamais accepté en silence non plus. La dérive se propage — une ancre que la réconciliation, le masquage ou la réingestion fait passer à l'état détaché détache la citation qui la pointait.

text #161 958a6c6e

Restent théoriques, faute de la phase G et des écrans : les trois états affichés et le retour visuel à la source depuis une citation. Le lien, lui, est peuplé.

section_header #162 889f9143

6.4 pgvector : pas installé, et pas nécessaire tout de suite

text #163 b27e6df9

Vérifié : docker-compose.yml:50 utilise postgres:17-alpine (pas l'image pgvector), pyproject.toml ne contient ni pgvector ni sentence-transformers, et aucun modèle ne porte de champ embedding . C'est planifié (PHASE-33, PLAN/README.md:126 ), pas fait. SPEC-selection-des-preuves.md § 7 tranche qu'il ne faut pas l'attendre : à l'échelle d'un carnet — quelques centaines d'extractions — un cosinus O(n²) en numpy suffit largement ; pgvector sert la recherche à l'échelle de la base entière, ce qui est un autre problème.

section_header #164 4626afef

6.5 Note sur les documents de référence

text #165 67267211

Quatre specs se complètent, et elles doivent être lues dans cet ordre :

text #166 85c0c9f8

SPEC-ancrage-par-element-v2.md v2.0 | ancrage — élément, portion, réconciliation | oui, déposée le 8 août 2026 — implémentée , avec trois écarts documentés ; phases H-K (branchement) non commencées | SPEC-corpus-base-carnet-note.md v1.1 | corpus — base, carnet, note, catégories | oui — implémentée (phases A-H), reste la phase I et la migration 3 | SPEC-synthese-carnet.md v1.0 | synthèse — deux genres, sourcing, vérification | oui — implémentée hors interface (phases A-G), 16 addendums en tête de fichier ; H-I à faire | SPEC-selection-des-preuves.md v1.0 | sélection — regroupement, oppositions, audit | oui — non entamée |

table #167 4f0fc7f6
SpecCoucheDans le dépôt
text #168 fc69e45c

Les quatre sont maintenant dans le dépôt. Celle d'ancrage y figure bien qu'elle soit déjà implémentée, et c'est délibéré : le code porte ce qui a été retenu, jamais ce qui a été écarté ni pourquoi. Trois de ses décisions ont d'ailleurs changé en cours de route — son § 5.1 violait une contrainte d'unicité dès la première ligne (les contraintes sont devenues DEFERRABLE ), element_parent a été retiré, et EtatAncrage réduit à deux valeurs. Ces écarts sont notés en tête du fichier et au CHANGELOG.md .

text #169 ca2b48cc

La même discipline vaut pour la spec de synthèse, qui porte en tête seize addendums datés : ce sont les décisions prises pendant l'implémentation et les trous que les relectures ont révélés. On ne réécrit pas la spec pour lui donner raison après coup ; on écrit à la suite ce qu'on a découvert.

text #170 9dab8d9a

hypostasis_extractor/services/ , CHANGELOG.md front/services/rendu_elements.py hypostasis_extractor/services/ingestion_docling.py Page.dossier core/services/corpus.py , front/views_corpus.py , migrations core.0040-0045 SourceLink peuplé, écartées, couverture, gardes, applieur) | fait, phases A-F | core/services/synthese.py , core/services/section_ops.py , migrations core.0046-0052 core/services/verification.py , migration core.0052 SPEC-synthese-carnet.md § 13 | PLAN/bascule-css-cahier-des-charges.md SPEC-selection-des-preuves.md SourceLink core/models.py:1439 , core/services/synthese.py parent_page ) | dormant : schéma conservé, plus aucun écrivain | front/tasks.py docker-compose.yml:50 , pyproject.toml PLAN/audit-ux-ui-2026-08-08.md

table #171 4385a00e
ChantierÉtatPreuve
Moteur d'ancrage par élémentfait, migré,non branchéà l'UI d'analyse (phases H-K non commencées)
table #172 6823923a
Bascule CSS (design de la maquette, à feature constante)décidée, cahier des charges rédigé, non engagée
table #173 e09ad4f3
schéma completet écrit en production
section_header #174 16fdb5b3

7. Les maquettes comme étalon

text #175 1ff40b50

tmp/maquettes/ contient trois écrans navigables — corpus.html (le carnet : facettes, wikis, synthèses dirigées, alignement), selection-preuves.html (le regroupement, les oppositions, le panneau « pourquoi cette extraction est là »), maquette.html (la note : lecture par éléments, sourcing, bascule de carnet) — servis par un index.html qui les présente. Ce ne sont pas des dessins : ce sont des programmes, et leur raison d'être est d'un autre ordre que l'illustration.

text #176 785cb291

La règle du corpus unique. Tout part de donnees.js , dont l'en-tête fixe la discipline : les trois écrans affichaient chacun leur propre copie des données — « trois copies, trois vérités », avec des offsets d'ancrage faux dans 19 cas sur 20. Désormais, on n'écrit dans donnees.js que ce qu'un humain ou un LLM produit vraiment — un texte, une citation, un commentaire — et tout le reste est dérivé par des fonctions : offsets, comptes, écartées, couverture, similarités. « Si un chiffre est tapé quelque part, c'est un défaut. » Quatre choses restent moquées, et c'est assumé parce qu'elles ne peuvent pas se déduire du reste : les textes (qui tiennent lieu de sortie de Docling et de l'extracteur), les commentaires (la délibération humaine), les positions 2D (la projection d'embeddings), la liste des oppositions (la seconde passe LLM).

text #177 b10bd0aa

37 contrôles Playwright. scripts/verifier_les_maquettes.py pilote un navigateur et vérifie 37 assertions. Les plus significatives ne vérifient pas des nombres figés mais des invariants — c'est-à-dire exactement ce que le futur back devra garantir :

text #178 01ceb1ae

écartées = périmètre − citées , recalculé dans la page au moment du test ;

list_item #179 696ecd78
  • la couverture est une vraie jointure (recomptée indépendamment) ;
text #180 fb4e3722

le garde-fou « une synthèse n'est jamais source » est exercé : le carnet contient des synthèses, la portée d'une nouvelle doit compter exactement les notes ordinaires ;

list_item #181 4105ee15
  • une opération de section au titre introuvable est visiblement rejetée ;
text #182 7051a8bc

chaque citation se retrouve littéralement dans le texte de son élément (sinon l'ancrage affiché serait faux) ;

text #183 46724ade

tous les liens de preuve aboutissent — ouverts un par un, dans une seconde page, jusqu'au surlignage effectif du passage.

text #184 e0615a85

Ce qui n'existe pas encore est marqué. Les fonctionnalités cibles portent une pastille « cible » avec leur justification en infobulle : le fil d'Ariane (« remplace l'arbre de navigation, que le N-N rend impossible »), le lecteur audio (« aucun lecteur n'existe dans le produit »), la progression WebSocket (« ne pousse aujourd'hui que "terminé" »). La maquette ne laisse pas croire que le produit fait ce qu'il ne fait pas — c'est la même exigence de transparence que le § 3, appliquée à elle-même.

text #185 243b6bf3

Pourquoi « étalon ». Quand le back sera branché, on rejouera les mêmes contrôles contre le vrai moteur et on comparera les sorties. Les specs elles-mêmes se placent sous cette autorité : « toute divergence entre cette spec et la maquette est un défaut de l'une des deux, à trancher avant de coder » ( SPEC-synthese-carnet.md , en-tête). La maquette n'est pas une promesse d'interface ; c'est un banc de test du comportement, écrit avant le code qu'il testera.

text #186 1ba423ad

Le mécanisme a commencé à jouer son rôle. L'écran carnet du 8 août a été construit contre l'étalon, à partir d'un cahier des charges qui listait les écarts et sept arbitrages entre la spec et la maquette ( PLAN/corpus-phase-f-cahier-des-charges.md ) ; le filtre corpus « une synthèse n'est pas une source » est exactement l'invariant que l'étalon exerçait ; et cinq scénarios de bout en bout rejouent dans un vrai navigateur, sur le produit réel, ce que la maquette montrait. La décision de bascule CSS (§ 6.2) est la conséquence logique de cette autorité : si l'étalon fait foi sur le comportement, il n'y a pas de raison qu'un CSS que personne ne défend fasse foi sur la forme.

section_header #187 878a52fc

Sources

text #188 1e07e45a

Dans le dépôt : SPEC-synthese-carnet.md (et ses seize addendums), SPEC-corpus-base-carnet-note.md , SPEC-selection-des-preuves.md , SPEC-ancrage-par-element-v2.md , PLAN/INSPIRATION_ATOMIC.md , PLAN/README.md , PLAN/bascule-css-cahier-des-charges.md , PLAN/corpus-phase-f-cahier-des-charges.md , PLAN/audit-ux-ui-2026-08-08.md , CHANGELOG.md (entrées des 5, 8 et 9 août 2026), tmp/maquettes/ (dont donnees.js ), scripts/verifier_les_maquettes.py , core/models.py , core/services/ ( corpus.py , synthese.py , section_ops.py ), front/views_corpus.py , front/tasks.py , hypostasis_extractor/models.py , hypostasis_extractor/services/ , front/services/rendu_elements.py , core/management/commands/basculer_vers_le_moteur_element.py .

text #189 dfdb4eb6

Notes de la mémoire Atomic (août 2026) : « Sourcing et attribution dans les systèmes RAG — état de l'art 2026 » (benchmarks cités : ALCE, LongBench-Cite, CiteControl, FACTS ; outils : RAGAS, MiniCheck, Vertex AI check-grounding, Anthropic Citations API ; standards : W3C Web Annotation, Media Fragments URI) ; « Praxis (En commun / Projet collectif) — plateforme de gestion de corpus à trois niveaux » ; « Hypostasia V3 — couche corpus et refonte de la navigation » ; « Atomic — base de connaissances personnelle local-first » ; « Trois moteurs RAG open source décortiqués ».