Les planches d’ancrage · série II · feuille 9 / 19

L’axe des données : classer

L'AXE DES DONNÉES — par quoi une donnée est classée, et jusqu'où la classe tient

Planche 9 · L’axe des données : classer
32 pièces · 14 bâties · 10 partielles · 7 absentes · 1 non établies
Planche 9 — L’axe des données : classer

ouvrir la planche en plein écran → · 32 pièces · 20 traverses · 15 marges

La thèse une ligne, écrite dans la matière

Les deux axes sont bâtis, mesurés et tenus par la CI dans le canon — mais la mesure qui classe ne lit jamais le canon (zéro import dans les trois modules qui classent), elle applique sa propre table de 207 mots qui n'a aucune entrée pour S0 ; et en bas, la classe est durable sur le NOM (hubs, missions, départements portent sphere_primary et canal en base) et volatile sur le VERBE (l'événement classé vit dans une liste RAM de 1000, zéro session ouverte), la table durable des événements n'en recevant qu'un champ sur cinq.

Les marges 15 arbitrages ouverts, entiers
  • ⚑ CE QUE J'AI MESURÉ, ET AVEC QUOI. Commit 8addf1c94 (git show HEAD:…) pour tous les fichiers backend/, hf-atom-tools/, alembic/, frontend/js/message-bus.js et .github/workflows/ci.yml. Interpréteur pour les mesures par appel et par AST : backend/.venv-tests/Scripts/python.exe. ⚠ UNE EXCEPTION À DIRE : main.py diffère de l'arbre (+209/-38) ; j'ai re-cité TOUS ses numéros au commit, et j'ai marqué comme « arbre de travail seulement » le middleware de refus par chemin de routeur cassé.
  • ⚑ LES CHIFFRES QUE J'AI RE-MESURÉS ET QUI TIENNENT : 34 libellés (faces 16/11/7) · 8 branches · VALID_CANALS = 16 · _VALID_SPHERES = 10 · REGISTRY = 96 · core ALL_TOPICS = 302 · babel ALL_TOPICS = 39 · _max_history = 1000 · 12 tests arbre_canon · 12 tests spheres_libelles · CORPUS_DEFINITIONS = 5 · MASTER knowledge-threads = 5 425 lignes · docs/knowledge-threads = 6 fichiers.
  • ⚑ LES CHIFFRES QUE J'AI MESURÉS AUTREMENT QUE L'AGENT : le dénominateur « 1245 » n'existe nulle part dans le dépôt canonique — ses seules occurrences vivent dans .claude/worktrees/ et y sont des numéros de ligne. Le périmètre que les clés du registre nomment (`app.*`) compte 1726 .py au commit. Donc 96/1726 = 5,6 %, et la phrase juste est « 96 modules étiquetés sur les 1726 fichiers .py de backend/app/ au commit 8addf1c94 ».
  • ⚑ LES DEUX ARBRES, DITS. backend/app/ = 1726 .py suivis par git au commit ; backend/ hors app/, tests/ et alembic/ = 309 .py. Les trois « zéro écrivain » de cette planche (babel_corpus_word_cooccurrences, status="red", persistance de l'Event) ont été cherchés dans LES DEUX, et je le dis à chaque fois. Note : les 309 du second arbre sont structurellement hors du REGISTRY — ses 96 clés sont toutes de forme `app.…`.
  • ⚑ TROUVÉ EN CHEMIN, NON ANNONCÉ : SPHERE_KEYWORDS n'a PAS de clé « 0 ». S0 Communication — la seule sphère qui a un organe bâti et nommé — est la seule que l'analyseur ne sait pas reconnaître positivement. Elle n'est atteinte que par échec, à l'étape 5, et marquée red pour cela. La sève est la seule chose que la mesure ne peut qu'inférer.
  • ⚑ TROUVÉ EN CHEMIN : CINQ jeux de noms de sphères coexistent, et l'intersection se mesure. Sur les 10 valeurs de SphereType — la forme que la donnée prend EN BASE — sphere_de() n'en résout que 2. Le dépôt a déjà payé ce prix une fois et l'a écrit : nova_pipeline.py:795-798, « l'intersection des deux vocabulaires valait UNE seule clé, institutions ». Le même piège est encore ouvert entre le canon et l'ORM.
  • ⚑ TROUVÉ EN CHEMIN : le garde CI de la taxonomie et la décision 34 ne nomment pas le même canon. lint_sphere_dedup.py:31 déclare canonique backend/app/models/sphere.py ; CLAUDE.md décision 34 déclare canonique spheres_libelles.py + arbre_canon.py. Et le lint n'attrape qu'un NOM DE CLASSE : aucune copie en dict ne lui est visible, et routing_engine.py — qui porte 9 sphères sans S6 — est nommément exclu (:50-58). Une garde qui exclut le cas qu'elle existe pour attraper.
  • ⚑ RÉFUTATION LA PLUS LOURDE : le joint canon → mesure n'existe pas. 0 import de atom_canon dans event_bus.py, 0 dans sphere_context_analyzer.py, 0 dans master_hash_registry.py. La taxonomie EST le canon — et ce n'est pas elle qui classe. ⚠ Je refuse d'en faire un défaut d'installation : backend/Dockerfile:43-44 fait COPY /opt/hf-atom-tools PUIS pip install -e, donc le paquet EST importable en conteneur. C'est un défaut de LECTURE, pas de livraison.
  • ⚠ CE QUE J'AI RÉFUTÉ DANS L'AUTRE SENS — deux défauts annoncés qui n'en sont plus. (1) « La porte fractale lit la RAM, jamais la base » : FAUX au commit. fractal_scope_guard.py:70-78 refuse sans base (fail-close), :79-91 valide en base, et l'appelant HTTP passe la session. Il reste un interstice nommé : un caller_id non-UUID saute la validation G1. (2) « La table durable des événements n'a AUCUNE colonne pour les recevoir » : timeline_events EN A UNE, sphere_id (v80_017:42), indexée. Ce qui manque, c'est les quatre autres — et le joint avec le bus.
  • ⚑ LA CORRECTION DU PIÈGE DE TRONC. CLAUDE.md dit « deux modules du même paquet, deux réponses ». Mesuré par appel : les deux réponses sortent du MÊME fichier — spheres_libelles.py:79 range « TRONC » comme face DONNÉES de S0, et spheres_libelles.py:151 déclare TRONC = "S9". 72 lignes d'écart. L'épreuve qui confronterait les deux existe sur le disque (test_spheres_libelles_la_face_est_attestee.py) mais PAS dans HEAD : elle n'est pas commitée, donc elle ne garde rien.
  • ⚑ LA CORRECTION DU CHIFFRE T3. from_router() a 3 appels RÉELS : babel_macro_decoder.py:533, bridge_proposer.py:220 et services/bioacoustic/bridges/babel_tier3.py:99. bridge_manager ne l'appelle PAS — ses trois occurrences sont des docstrings. Compter des noms ne voit pas des appels : c'est la loi, et elle vient de se vérifier sur ce chiffre précis.
  • ⚑ LA CORRECTION DE PÉRIMÈTRE DU SHA-256. Le vecteur-hachage est dans backend/app/services/embeddings/multi_model_embedder.py:46, PAS dans babel/. Les deux clients d'embeddings de Babel n'emploient aucun hachage (OpenAI 1536 · SentenceTransformer 384). Dire « les embeddings sont un SHA-256 » sans nommer le module est exactement la faute de la ligne Supabase : un fait vrai sous sa règle, faux dans la phrase qui l'énonce.
  • ⚠ CE QUE JE N'AI PAS ÉTABLI, et ce qui manque pour l'établir. (a) « 2 documents sur 122 » : le corpus source vit hors dépôt (OneDrive) ; j'ai confirmé les deux chiffres du dépôt (6 fichiers, 5 425 lignes), pas le ratio. (b) Aucune mesure en prod : lecture seule, aucune base interrogée — donc je ne dis RIEN sur le peuplement réel de timeline_events, de babel_corpus_word_hz_measured ou de missions.canal. Une session qui voudra le savoir doit nommer la base (atom-db / atom_db) et poser la question en SQL. (c) Je n'ai pas compté les mots-clés de routing_engine.SPHERE_KEYWORDS (la seconde table) : je sais seulement qu'elle existe, qu'elle est bilingue et qu'elle est distincte des 207.
  • ⚠ DEUX DÉRIVES DE RENVOI, à corriger quand on y touchera. mission_orm.py:45 renvoie à « event_bus.py:223-228 » pour VALID_CANALS — l'énum est à :289-296 au commit. Et CLAUDE.md annonce message-bus.js à 1359 lignes : il en fait 1480 au commit. Aucune des deux ne change une conclusion ; les deux font qu'un lecteur pressé regarde à côté.
  • ⚑ LA FORME DU DÉFAUT, UNE FOIS NOMMÉE. Ce n'est pas que la classification manque : les deux axes sont bâtis, testés, gardés par la CI, et trois tables en base portent déjà sphere_primary. C'est que le système sait classer, sait se dénoncer quand il échoue — et que personne ne lit sa dénonciation, ni ne range son résultat. 4 écritures de status="red", 0 lecteur. 5 champs de classe sur l'enveloppe, 1 dans la table durable, 0 session ouverte par le bus. Le refus honnête est bâti ; c'est l'oreille qui manque.
Les blancs déclarés ce que la mesure n’a pas pu voir

Toute suite de cette planche doit, pour chaque chiffre : (1) nommer le fichier ET la ligne au commit, et dire « dans l'arbre » quand elle mesure l'arbre de travail ; (2) nommer les DEUX arbres fouillés — backend/app/ (1726 .py) et backend/ hors app//tests//alembic/ (309 .py) — avant d'écrire « zéro » quoi que ce soit ; (3) ne jamais rendre un ratio sans sa règle de normalisation : « 96 sur 1245 » a traversé une vague entière sans que personne ne puisse dire ce qu'était 1245, et ce n'était rien ; (4) distinguer un appel d'une mention — trois docstrings de bridge_manager se sont fait compter comme trois appels de from_router() ; (5) ne jamais écrire « les embeddings » ni « le registre » ni « TRONC » sans dire LEQUEL : le dépôt porte deux familles d'embeddings, cinq tables de sphères et deux TRONC, et chaque fois l'un contredit l'autre sous le même mot. Et pour toute pièce déclarée BÂTIE : dire qui l'appelle, à quelle ligne — un routeur hors main.py n'est pas atteignable, une surface sans écrivain n'est pas bâtie, un champ écrit sans lecteur n'est pas une garde.

ne pas dessiner

  • Les 7 planches cosmogrammes existantes (frontend/planches/*.svg) : QG, poumons, Metatron, grille 27, urbanisme, contenu, anatomie des lieux
  • Les planches techniques déjà rendues : bilatéral, respiration, layers/scopes, portes-fenêtres, meshmap, emboîtement, étoile-semence
  • Les planches institutionnelles déjà rendues : économie, armure, politique, institutions, entreprise
  • La grille 27+0 et le compte des cellules — déjà tenue par arbre_canon.cellules_grille_27 et déjà dessinée
  • Le débat « S6 est-elle une branche » : tranché 2026-07-20, tenu par 12 épreuves vertes en CI. NE PAS RE-ARBITRER — la planche ne fait que montrer où S6 reste injoignable (routing_engine), pas si elle existe
  • Le débat sur la base de production (PostgreSQL auto-hébergé, pas Supabase) : tranché et mesuré ; cette planche ne touche aucune base
  • L'anatomie de Babel dans son ensemble (45 tables, 59 modules, 9 endpoints) : seule la cascade de mesure T1→T2→T3 entre ici
Les traverses 20 relations, de source à source
spheres_libelles.py (canon, 34 libellés)
devrait fournir la table de résolution
→event_bus._resolve_sphere_primary (la mesure)0 occurrence de « atom_canon » dans backend/app/services/event_bus.py
spheres_libelles.py
devrait dériver les 207 mots-clés
→sphere_context_analyzer.SPHERE_KEYWORDS0 occurrence de « atom_canon » dans backend/app/services/sphere_context_analyzer.py ; la table est écrite à la main, :71-137
spheres_libelles.sphere_de
traduit les 9 membres vers les codes canon
→routing_engine.SphereId (9 valeurs FR)routing_engine.py:39 (import), :240-251 (_index_indices), :311-312
spheres_libelles.sphere_de
devrait résoudre les valeurs persistées
→models/sphere.py SphereType (10 valeurs EN, la forme en base)mesuré par appel : 2/10 résolues (institutions→S3, entertainment→S6), 8 muettes
master_hash_registry.REGISTRY (96)
fournit le défaut du module émetteur
→event_bus (étape 2 sphère + inférence canal)event_bus.py:346-350 (sphère) et :381-385 (canal)
event_bus.publish
écrit le marqueur d'échec de classification
→status="red" (le refus honnête)event_bus.py:468, :478, :483, :528
status="red"
devrait déclencher une garde, une alerte, un tri
→un consommateur qui en décide0 lecteur dans backend/app/services/ et backend/app/routers/ ; le dépôt interdit même de s'en servir, routers/event_bus.py:380-383
event_bus.Event (5 champs de classe)
devrait persister la classe du verbe
→une table durableevent_bus.py:284-285, :500-502 — liste RAM plafonnée à 1000 ; aucune session ouverte dans le fichier
event_bus
devrait alimenter le rangement durable
→timeline_service (la table durable qui a sphere_id)timeline_service.py n'importe pas event_bus ; aucun abonnement. Les deux chemins sont disjoints.
timeline_events.sphere_id
devrait les porter aussi
→les 4 autres champs de classe (canal, affluent, ratio, status)v80_017_timeline_events.py:38-48 — 11 colonnes, une seule de classification
VALID_CANALS (énum fermée de 16)
devrait contraindre la colonne
→missions.canal en baseVARCHAR(20) DEFAULT 'bus' sans CHECK (v81_029:185, v82_123:54) ; mission_orm.py:44-48 recopie l'énum en commentaire
measure_tier1 (lecteur)
lit les co-occurrences pour estimer Hz
→babel_corpus_word_cooccurrences (la table)measure_tier1.py:63-82
un ingesteur de corpus
devrait peupler la table
→babel_corpus_word_cooccurrences0 écrivain dans backend/app/, dans le second arbre backend/ hors app/, et dans le reste du dépôt hors tests
measure_cascade
instancie le client de mesure
→LLMClient (T3)measure_cascade.py:715 `LLMClient()` → AsyncOpenAI (llm_client.py:99-101) ; from_router() local-first existe mais n'est PAS emprunté ici
_clamp_reading (clamp η 0,3)
borne la confiance hors S1 et se compte
→chaque lecture T3measure_tier3.py:166-196
multi_model_embedder._base_vector (SHA-256)
serait remplacé par ATOM_EMBEDDINGS_PROVIDER
→un vrai modèle sémantiquemulti_model_embedder.py:191-204 implémenté ; la variable n'est posée dans aucun fichier de déploiement du dépôt
fractal_decode_router (HTTP)
passe la session pour que la portée se valide en base
→fractal_scope_guard (la porte)fractal_decode_router.py:42, :54-56 ; fractal_scope_guard.py:70-91
lint_sphere_dedup.py (garde CI)
devrait signaler la taxonomie rivale
→routing_engine.SphereIdlint_sphere_dedup.py:50-58 — le fichier est nommément exclu
CI (ci.yml:474)
les exécute à chaque passage
→les 12+12 épreuves du canon des sphères.github/workflows/ci.yml:460-474, job canon-tests, pytest hf-atom-tools/atom_canon/tests/
frontend/js/message-bus.js
devrait classer ce qu'il publie
→les deux axesmessage-bus.js:410 (sphereId reçu, non résolu) ; aucun VALID_CANALS dans frontend/js/
La nomenclature 32 pièces, chacune avec sa source
1Registre canon des libellés — 34 libellés, 3 faces34 libellés — répartition par appel : opérationnelle 16 · données 11 · sectorielle 7. Les trois comptes de CLAUDE.md sont CONFIRMÉS par exécution, pas par lecture.hf-atom-tools/atom_canon/primitives/spheres_libelles.py:60-65 (enum Face), :72-146 (LIBELLES), :171-183 (sphere_de) · confirmé dans le codeAppelé par 5 modules backend : app/data/agents_fallback.py:31 · services/community_branches.py:77 · services/fractal_position_resolver.py:708 · services/routing_engine.py:39 · scripts/lint_proposal_contract.py:31. Tenu par 12 épreuves (hf-atom-tools/atom_canon/tests/test_spheres_libelles.py, 12 `def test`) exécutées en CI (.github/workflows/ci.yml:474).
2Les TROIS faces — la face est la dimension du NOM, pas de la chose3 faces, 34 noms. libelle_de(sphere, face) :199-204 rend le premier attesté pour une face.spheres_libelles.py:60-65 — OPERATIONNELLE (runtime/code/flux) · DONNEES (persistance/ORM) · SECTORIELLE (domaine d'invocation §0.5) · confirmé dans le code
3arbre_canon — 8 branches + tronc S9 + sève S0, S6 INCLUSElen(BRANCHES) == 8 par appel. 12 épreuves (test_arbre_canon.py, 12 `def test`), en CI ci.yml:474. Importé par le backend : routers/fractale_communautaire.py:99.hf-atom-tools/atom_canon/primitives/arbre_canon.py:76 BRANCHES, :79 TRONC="S9", :82 SEVE="S0", :150-152 est_une_branche, :155-157 cellules_grille_27 · confirmé dans le code
4Le piège de TRONC — et il ne tient PAS deux modules, il tient UN SEULPar appel : sphere_de('TRONC') → S0 · spheres_libelles.TRONC → S9 · arbre_canon.TRONC → S9.spheres_libelles.py:79 Libelle("TRONC", Face.DONNEES) rangé sous la clé "S0" → _INDEX["tronc"]="S0" (:160-164) ; spheres_libelles.py:151 TRONC="S9" ; arbre_canon.py:79 TRONC="S9" · confirmé dans le codeCORRECTION : CLAUDE.md présente le piège comme « deux modules du même paquet, deux réponses ». La mesure dit mieux et pire — les deux réponses sortent du MÊME fichier, spheres_libelles.py, à 72 lignes d'écart. Aucun test ne les confronte au commit (test_spheres_libelles_la_face_est_attestee.py existe sur le disque mais PAS dans HEAD).
5VALID_CANALS — l'énum fermée de 1616 par AST : bus · http · ws · sse · email · sms · voice · chat · stream · push · blockchain · xr · voxel · hologram · dom · none. CONFIRMÉ.backend/app/services/event_bus.py:289-296 · confirmé dans le code
6Pipeline de résolution de sphère — 5 étapes, premier qui match gagne5 étapes : (1) data['_sphere_primary'] · (2) REGISTRY[source].sphere_id · (3) top_n sur le nom de topic · (4) top_n sur nom+data[:500] · (5) repli "0".event_bus.py:325-375 (_resolve_sphere_primary), appelé par publish() :456-457 ; canal : :378-388 (_resolve_canal), appelé :471-472 · confirmé dans le code
7BABEL /measure — atteignable, et fail-close sans base5 points REST sur ce routeur : /measure, /health, /dissonance, /phrase, /word/{word}.backend/app/routers/babel_router.py:102 prefix="/api/v2/babel", :297-332 POST /measure ; monté par backend/app/main.py:1961 (au commit) ; _require_db :164-176 → 503, aucun repli mémoire · confirmé dans le code
8⚑ T3 — le clamp η 0,3 est réellement bâti, et il se compteAppliqué PAR LECTURE, pas seulement au résultat final (:21). Pondération par flag :62-65 [S1 1.0 · S2 0.7 · S3 0.5 · S4 0.3].backend/app/services/babel/measure_tier3.py:53 MAX_INTERPRETIVE_CONFIDENCE=0.3 ; :55 VALID_DISCIPLINE_FLAGS={S1,S2,S3,S4} ; :166-196 _clamp_reading ; compteur :178-195 · confirmé dans le codeRien ne manque, et la pièce porte une qualité rare : le compteur refuse d'inventer la sphère qu'il ne connaît pas (:182-188, sphere="non_transmise", avec la raison écrite). C'est l'inverse exact du repli silencieux.
9T3 — le client par défaut sort de la machine (et le compte annoncé est faux d'un nom)CORRECTION : from_router() a 3 appels RÉELS — babel_macro_decoder.py:533 · bridge_proposer.py:220 · services/bioacoustic/bridges/babel_tier3.py:99. bridge_manager ne l'appelle PAS : ses 3 occurrences (:129, :143, :601) sont toutes dans des docstrings. Et measure_cascade ne l'appelle pas : CONFIRMÉ.measure_cascade.py:715 `llm_client = llm_client or LLMClient()` → backend/app/services/babel/llm_client.py:99-101 `client or AsyncOpenAI()` · confirmé dans le codeLa cascade canon est le seul chemin qui n'emprunte pas la voie locale (llm_client.py:57 DEFAULT_ROUTER_MODEL_ID="gemma4:e4b", gratuit). Le chemin qui MESURE le canon est le seul qui paie et qui sort.
10⚑ EMBEDDINGS, 13e pilier — le vecteur EST un SHA-256CONFIRMÉ dans le code.backend/app/services/embeddings/multi_model_embedder.py:46 `hashlib.sha256(text.encode("utf-8")).digest()` dans _base_vector ; :105 sel par model_id ; :148-164 provider_status → mode="deterministic_hash" · confirmé dans le codeCORRECTION DE PÉRIMÈTRE, qui compte : ce n'est PAS le module d'embeddings de Babel. babel/embeddings_client.py (OpenAI, 1536) et babel/local_embeddings_client.py (SentenceTransformer, 384) n'emploient AUCUN hachage. Deux familles d'embeddings coexistent dans le dépôt ; une seule est un SHA-256. Dire « les embeddings sont un hachage » sans nommer le module recopie l'erreur de la base Supabase.
11corpus_count = 5 n'est pas un compte de documentsCORPUS_DEFINITIONS = 5 DÉFINITIONS de corpus : C1 doctrine · C2 code · C3 vocabulaire · C4 memoire · C5 externes. CONFIRMÉ.backend/app/services/embeddings/embedding_orchestrator.py:160 `"corpus_count": len(CORPUS_DEFINITIONS)` ; backend/app/services/embeddings/models.py:13-19 · confirmé dans le codeLa correction de l'agent est juste, et il existe un homonyme qui, lui, compte vraiment : storage_repository.py:181 calcule corpus_count par `COUNT(DISTINCT corpus_id)` en base. Deux champs, même nom, l'un structurel et l'autre mesuré — le même genre de piège que les deux TRONC.
12Ce qui EST durable : la classe sur le NOM3 tables portent sphere_primary en base ; 1 seule (missions) porte AUSSI canal. hubs est la seule à porter les trois nuances de l'axe 1.hubs : v81_029_cycles_engine_tables.py:87-89 (sphere_primary NOT NULL · sphere_affluent · sphere_ratio DEFAULT 1.0), index :103 — missions : v81_029:181 sphere_primary, :185 canal NOT NULL DEFAULT 'bus', index :192 ; re-contractualisé v82_123_mission_center_missions_contract.py:53-54, 69-70, 92, 99 — departements : v81_032_departements_table.py:115 sphere_primary, index :141 · confirmé dans le codeRien côté schéma. C'est la contre-épreuve de la thèse : la classification sait être durable — elle l'est pour ce qui a un nom et une ligne. Ce qui n'en a pas, le verbe, ne l'obtient jamais.
13register_router n'avale plus les ImportError — et le registre est exposéCONFIRMÉ au commit. ⚠ La forme plus riche (prefix + route_paths par routeur cassé, et un middleware qui rend un refus nommé sur les chemins d'un routeur mort, backend/app/main.py:1633-1670 de l'ARBRE DE TRAVAIL) n'est PAS au commit : main.py diffère de +209/-38 lignes.backend/app/main.py:1637-1663 (au commit) : _registered_broken.append({name, module, type, error}) ; core=True → RuntimeError ; logger.error sinon ; REGISTER_ROUTER_STRICT → RuntimeError. Registre déclaré :1634, exposé :2610-2613, état de santé :3003, :3018, :3028 · confirmé dans le codeREGISTER_ROUTER_STRICT n'est posé dans AUCUN workflow (git grep → scripts/audit_router_imports.py:26 en docstring, scripts/banc_non_regression.py:82 en liste d'environnement). Le garde CI équivalent existe sous un autre nom : .github/workflows/ci.yml:220 `audit_router_imports.py --max-broken 0`.
14core/bus_topics.py se contredit dans le même fichier — et l'assert gagnePar AST : ALL_TOPICS contient 302 éléments. L'assert S'EXÉCUTE à l'import — 302 est la vérité du runtime, 283 est de la prose. Écart 19, CONFIRMÉ.backend/app/core/bus_topics.py:4 « 283 topics total » · :1043-1044 « asserted to have exactly 283 unique » · :1381-1382 `assert len(ALL_TOPICS) == 302` · confirmé dans le codeLa prose. Par comparaison, backend/app/services/babel/bus_topics.py est cohérent de bout en bout : :3 « 39 topics au total », :302-303 `assert len(ALL_TOPICS) == 39`. Le catalogue de Babel dit vrai ; celui du cœur dit deux choses.
15Étape 2 — le REGISTRY des modules étiquetés : 9696 entrées CONFIRMÉES par AST (= 96 `sphere_id=` = 96 `canal_default=`). ⚠ Le DÉNOMINATEUR 1245 est NON ÉTABLI : zéro occurrence de « 1245 » dans le dépôt canonique (les seules vivent dans .claude/worktrees/, et ce sont des numéros de ligne). Mesure directe du périmètre que les clés du registre nomment (`app.*`) : git ls-tree -r HEAD -- backend/app | grep -c '\.py$' = 1726. Donc 96/1726 = 5,6 %, pas 7,7 %.backend/app/core/master_hash_registry.py:96 (REGISTRY), 96 ModuleDescriptor ; lu par event_bus.py:346-350 et :381-385 · confirmé dans le codeLe numérateur est juste, la règle de normalisation manquait. Second arbre : les 309 .py de backend/ hors app/, tests/ et alembic/ n'ont AUCUNE entrée possible — les clés du registre sont toutes de forme `app.…`.
16⚑ Étapes 3+4 — l'analyseur par mots-clés, et le trou S0Par AST : SPHERE_KEYWORDS porte les clés '1'..'9' — PAS de clé '0' — et 207 mots au total. SPHERE_NAMES, elle, porte 10 clés dont '0'.backend/app/services/sphere_context_analyzer.py:71-137 SPHERE_KEYWORDS ; :140-151 SPHERE_NAMES ; :358-425 top_n ; seuils :407-414 (high ≥ 0,3 · medium ≥ 0,1 · low > 0) · confirmé dans le codeS0 n'a AUCUN mot-clé. L'analyseur ne peut donc JAMAIS classer positivement en S0 ; S0 n'est atteignable que par le repli de l'étape 5, c'est-à-dire par échec. La seule sphère qui a un organe bâti (communication_center) est la seule que la mesure ne sait pas reconnaître.
17⚑ CINQ tables de sphères coexistent — et seulement 2 valeurs sur 10 se traduisentMesuré par appel de sphere_de() sur les 10 valeurs de SphereType : 2 résolues (institutions→S3, entertainment→S6), 8 muettes (communication_transit, personal, business, creative_studio, community, education, my_team, sustainability → None).canon : spheres_libelles.LIBELLES (34 libellés) · mesure : sphere_context_analyzer.SPHERE_KEYWORDS (9 clés) + SPHERE_NAMES (10 noms FR sans accents) · transport : event_bus.py:299 _VALID_SPHERES (10) · base : backend/app/models/sphere.py:27-42 SphereType (10 valeurs ANGLAISES) + :45-60 SphereId ('0'..'9') · routage : backend/app/services/routing_engine.py:48-57 SphereId (9 valeurs FR) · confirmé dans le codeLa forme que la donnée prend EN BASE (SphereType) est à 80 % illisible pour le registre canon. Le registre « résout un nom vers sa sphère quelle que soit la face » — pour les faces qu'il a attestées, pas pour celle que l'ORM écrit. Le dépôt porte déjà la trace de ce prix : services/nova_pipeline.py:795-798, une table de contextes indexée sur les libellés français et relue avec la valeur anglaise, « l'intersection des deux vocabulaires valait UNE seule clé, institutions ».
18routing_engine — ici le canon EST branché, et S6 reste pourtant injoignable9 membres : personnel · entreprise · institutions · creation · communaute · communication · formation · mon_equipe · durabilite. Les 9 se résolvent au canon — vers {S0,S1,S2,S3,S4,S5,S7,S8,S9}.routing_engine.py:39 (import sphere_de), :240-251 (_index_indices construit {code canon → valeur routeur}), :311-312 (usage) ; l'enum local :48-57 · confirmé dans le codeS6 Divertissement n'a aucun membre. Le fichier le dit lui-même, :270-273 : « S6 Divertissement n'existe PAS dans SphereId de ce fichier : un indice pour S6 ne trouve donc aucune valeur et retombe sur les mots-clés. C'est un manque constaté, pas comblé ici. » Et ce fichier porte une SECONDE table de mots-clés (SPHERE_KEYWORDS bilingue, :63+), distincte des 207 de sphere_context_analyzer.
19⚑ Le garde CI de la taxonomie exclut nommément la taxonomie rivaleLe lint déclare canonique backend/app/models/sphere.py — PAS spheres_libelles.py. Il n'attrape qu'un NOM DE CLASSE (SphereType|SphereTypeEnum|SphereId). Il exclut explicitement backend/app/services/routing_engine.py et backend/app/services/village_population.py.scripts/lint_sphere_dedup.py — lancé en CI (.github/workflows/ci.yml:44-45) ; :26-29 le motif ; :31 le fichier déclaré canonique ; :50-58 les exclusions · confirmé dans le codeDeux trous. (1) Le garde et la décision 34 ne nomment pas le même canon. (2) Aucune copie en dict ne lui est visible : SPHERE_KEYWORDS, SPHERE_NAMES, LIBELLES, _VALID_SPHERES passent toutes sous son motif. Il garde un nom de classe, pas une taxonomie.
20T2 — la garde de dimension est bâtie ; c'est sa sortie qui est NonePôles de dimension ≠ celle du centroïde → écartés ; zéro pôle restant → Hz(value=None, method="tier2_dimension_mismatch"). Les deux dimensions en présence : 384 (local SentenceTransformer) et 1536 (OpenAI text-embedding-3-small).backend/app/services/babel/measure_tier2.py:233-241 ; défaut de client : measure_cascade.py:637-651 (_default_embeddings_client, local sauf opt-in) · confirmé dans le codeLe commentaire :233-236 nomme le défaut : « les pôles sont stockés à la dimension de leur seed ; le centroïde est à la dimension du client injecté ». Rien dans le code ne garantit que les deux coïncident ; la garde préfère honnêtement None à un Hz faux — mais elle rend None.
21Le 0,0 qui remplace l'absenceT1=None + T2=None + T3=None → la réponse HTTP porte hz=0.0.backend/app/routers/babel_router.py:322 `"hz": cascade.hz.value if cascade.hz.value is not None else 0.0` · confirmé dans le codeLe routeur fail-close proprement sur la base (503) et requalifie ensuite une absence de mesure en nombre. `method` et `audit_trace` disent la vérité dans la même charge ; `hz` dit 0,0. Un consommateur qui lit le champ nommé ne voit jamais le refus.
22L'interrupteur du vrai fournisseur — implémenté, jamais poségit grep ATOM_EMBEDDINGS_PROVIDER : multi_model_embedder.py + 2 tests + 2 scripts + des docs. ZÉRO fichier de déploiement — absent de backend/Dockerfile (qui ne pose que PYTHONPATH=/app, :24), de deploy/ et de infrastructure/.multi_model_embedder.py:27 REGISTRY_ENV="ATOM_EMBEDDINGS_PROVIDER" ; :191-204 _provider_from_env → BureauEmbeddingProvider ou SentenceTransformersProvider ; :206-230 le provider local · confirmé dans le codeLa variable, et elle seule. Le chemin de code est entier et testé ; non positionnée, _provider_from_env rend None et chaque vecteur retombe sur le SHA-256. ⚠ Le dire autrement — « le chemin n'est pas implémenté » — serait faux : il l'est.
23⚑ RÉFUTÉ — « la porte fractale lit la RAM, jamais la base »Au commit 8addf1c94, la porte HTTP passe la session et refuse honnêtement sans base. L'affirmation ne tient pas telle quelle.backend/app/services/fractal_scope_guard.py:70-78 (db is None → raise ValueError, fail-close), :79-91 (PersonPositionMembershipAuthority().avalidate_active_membership(db,…)), :99-105 (resolve_ancestors(…, db=db)) ; appelant HTTP : backend/app/routers/babel/fractal_decode_router.py:42 Depends(get_db_optional) → :54-56 passe db=db · confirmé dans le codeUn seul interstice subsiste, et il faut le nommer précisément : un caller_id NON-UUID laisse g1_subject=None (:66-70), la validation G1 est sautée sans refus, et resolve_ancestors retombe sur son repli mémoire via l'`except Exception` :103-104 → chaine=[caller_id] → refus. Le repli mémoire existe donc encore, mais il aboutit à un refus, pas à un faux accès. Et la voie WebSocket (ancetres résolus UNE FOIS à l'ouverture, décision A3) assume qu'une révocation n'agit qu'à la reconnexion : c'est écrit, :44-51.
24⚑ RÉFUTÉ en partie — la table durable des événements A une colonne1 champ de classification sur 5. Présent : sphere_id. ABSENTS de cette table : canal · sphere_affluent · sphere_ratio · status — aucun des quatre.backend/alembic/versions/v80_017_timeline_events.py:42 `sa.Column('sphere_id', sa.String(30), nullable=True, index=True)` ; écrivain : backend/app/services/timeline_service.py:204-238 (_persist_event, INSERT nommant sphere_id) ; monté : main.py:2268 (au commit) · confirmé dans le codeL'axe 2 tout entier, et les trois nuances de l'axe 1. Et le joint : timeline_service.py n'importe pas event_bus — il ne s'abonne à rien. Les deux chemins sont disjoints : le bus classe sans ranger, la timeline range un champ qu'elle reçoit de son appelant (record_event(…, sphere_id=…), :141-156) sans jamais le résoudre.
25⚑ RÉFUTÉ — « le canal est le SEUL axe qui refuse honnêtement »Les DEUX axes se dénoncent, au même endroit, dans la même fonction publish().event_bus.py:461-469 (axe sphère : repli S0 inféré + source non-S0-légitime → status="red") contre :481-484 (axe canal : hors énum → canal="bus" + status="red") · confirmé dans le codeL'affirmation d'exclusivité ne tient pas. La différence réelle est inverse de celle annoncée : le canal est COERCÉ vers une valeur valide (« bus »), donc la trace de l'erreur ne survit que dans status ; la sphère, elle, GARDE « 0 », une valeur parfaitement légitime. Le canal n'est pas plus honnête — il est plus réécrit.
26⚑ status="red" est écrit par un seul module et lu par PERSONNE4 écrivains, 0 lecteur. Périmètre fouillé : backend/app/services/ et backend/app/routers/.event_bus.py:468, :478, :483, :528 (les 4 écritures) ; recherche de '"red"' sur backend/app/services/ + backend/app/routers/ → 5 occurrences, TOUTES dans event_bus.py (la 5e est le docstring :79) · confirmé dans le codeLe dépôt le dit lui-même, mesuré : backend/app/routers/event_bus.py:380-383 — « le verdict ne se dérive JAMAIS de status. Mesuré : checkpoint.gate.approved — l'appelant vivant — rend status='red' par repli de sphère, avec blocked=None. Une garde posée sur status mettrait 403 à celui qu'on veut laisser passer. » Le refus honnête est écrit dans un champ que le système a appris à ne pas croire.
27⚑ LE JOINT CANON → MESURE N'EXISTE PAS0 / 0 / 0. Les trois modules qui classent réellement un événement n'importent jamais le canon.Comptes d'occurrences de « atom_canon » : backend/app/services/event_bus.py → 0 · backend/app/services/sphere_context_analyzer.py → 0 · backend/app/core/master_hash_registry.py → 0 · confirmé dans le codeC'est LE maillon. La décision 34 dit « la taxonomie EST le canon, plus un résumé » — et c'est vrai du module. Mais ce n'est pas lui que la mesure applique. ⚠ Je ne peux PAS en faire un reproche d'installation : backend/Dockerfile:43-44 fait bien COPY hf-atom-tools /opt/hf-atom-tools PUIS pip install -e, donc atom_canon EST importable en conteneur (même si les 5 importeurs calculent, eux, un chemin relatif `parents[3]/hf-atom-tools` qui vaut `/hf-atom-tools` en conteneur et n'existe pas — inoffensif grâce au pip install). atom_canon reste absent de backend/requirements.txt.
28⚑ T1 — une surface complète, et aucun producteur1 lecteur, 0 écrivain. Périmètre fouillé et nommé : backend/app/ (1726 .py), le second arbre backend/ hors app/tests/alembic (309 .py), et le reste du dépôt hors .claude/ et node_modules — zéro INSERT. Les seules autres mentions sont des mocks de test.backend/app/services/babel/measure_tier1.py:63-82 lit babel_corpus_word_cooccurrences ; schéma : backend/alembic/versions/v82_034_babel_phase1a_foundation.py:346-393 ; ORM : backend/app/models/babel_corpus_measure.py:77 · confirmé dans le codeL'écrivain. measure_tier1.py:6-7 le déclare lui-même : « requête en lecture seule — T5 ne peuple PAS la table ». Table vide → :129 et :151 rendent Hz(value=None, confidence=0.0, method="tier1_insufficient"). Le docstring :16 cite encore une promesse (« peuplée après premier appel ») que rien n'honore.
29⚑ L'événement classé ne se persiste NULLE PARTPar AST : _max_history = 1000. Recherche de session|AsyncSession|insert|commit|get_db|persist sur tout le fichier → 1 occurrence, et c'est le mot « persisted » dans un exemple de motif de topic (:203). Zéro session ouverte.backend/app/services/event_bus.py:284 `_event_history: List[Dict[str, Any]] = []` ; :285 _max_history=1000 ; :500-502 append + pop(0) ; :699 clear() · confirmé dans le codeLa table, et l'écrivain. L'enveloppe Event porte les cinq champs (event_bus.py:91-94 sphere_primary, sphere_affluent, sphere_ratio, canal ; :95 status) et to_dict les rend tous (:123-128) — vers une liste Python plafonnée à 1000, vidable par clear(). Lecture : get_history() :651-669, exposé par routers/event_bus.py:565 (monté main.py:1847). Le travail de classification de tout un processus tient dans 1000 cases et disparaît au redémarrage.
30⚑ L'énum du canal ne se referme PAS en base, et presque pas dans le codeHors event_bus.py et hors tests, VALID_CANALS n'est importé que par UN module applicatif : backend/app/services/qg_transversal_infrastructure_service.py:38, qui l'expose en liste triée (il ne valide rien).missions.canal = VARCHAR(20) DEFAULT 'bus' sans CHECK (v81_029:185, v82_123:54, 70) — recherche CheckConstraint sur backend/alembic/versions/ → 4 contraintes, aucune sur canal ; backend/app/models/mission_orm.py:44-48 recopie les 16 valeurs EN COMMENTAIRE et ne garde qu'un DEFAULT_CANAL="bus" · confirmé dans le codeLa fermeture. L'énum ne ferme qu'à l'intérieur de event_bus.publish() ; tout écrivain direct de missions.canal la contourne sans que rien ne le sache. Détail à corriger au passage : mission_orm.py:45 renvoie à « event_bus.py:223-228 » — l'énum est à :289-296 au commit. Le renvoi pointe à côté.
31⚑ Côté frontend, l'axe 2 n'existe pas du toutZéro VALID_CANALS / validCanals dans frontend/js/*.js. 13 occurrences de sphereId au commit, toutes en transport d'une valeur reçue. ⚠ 1480 lignes mesurées au commit, contre 1359 annoncées par CLAUDE.md — dérive.frontend/js/message-bus.js (1480 lignes au commit) : :410 `var sphereId = opts.sphereId || null` — option fournie par l'appelant, aucun pipeline de résolution ; :990-991 pose discipline_flag='eta_stricte' par défaut · confirmé dans le codeLes deux axes. Le seul champ canon réellement imposé côté navigateur est discipline_flag. La sphère est acceptée telle quelle, le canal n'est pas un concept. Le front ne peut donc ni classer, ni refuser, ni se dénoncer.
32Corpus foundation — 2 documents sur 122Ce que le DÉPÔT porte et que j'ai mesuré au commit : docs/knowledge-threads/ = 6 fichiers, dont CHENU_KnowledgeThreads_MASTER.md = 5 425 lignes. Les deux chiffres de la fiche sont CONFIRMÉS.fiche memory/corpus-foundation-importe-2-sur-122.md (le corpus source est hors dépôt : OneDrive) · non confirmé dans le codeLe numérateur 2, le dénominateur 122 et les 10 521 lignes de fond vivent dans un dossier hors dépôt que je n'ai pas ouvert. Lecture seule sur ATOM-CLEAN : la mesure reste à faire, ou à citer depuis la fiche en la nommant comme telle. Je refuse de la recopier comme un fait du code.
Le lexique de cette planche 28 termes à deux registres · 8 avec un jumeau

On ne remplace pas le nom canon, on le double : le terme reste l’autorité, la glose est une poignée. Le lexique entier est sur sa propre page.

portée (axe POSITION — ind / com / civ)Jusqu'où une chose compte — pour moi seul, pour un groupe, ou pour tous — et c'est l'endroit où l'on agit qui le décide, pas une case à cocher.trinity_scope (la portée-LENTILLE) — La mesure de combien une même chose pèse à la fois pour une personne, pour son groupe et pour tous — trois nombres ensemble, pas un choix…
Face (d'une portée)Le nom sous lequel une même portée se présente selon qui la regarde : la base, l'écran, l'agent, ou le livre du lecteur.
agent (type de nœud)Le genre de place qu'occupe un agent qui agit au nom de quelqu'un, avec l'endroit auquel il est rattaché.
communaute (type de nœud)Le genre de place qu'occupe un groupe, entre les personnes et le centre de tous.
sphère (S0 à S9)Elle dit de quoi un message parle : chaque message reçoit un numéro de 0 à 9 qui nomme son sujet.
S6 DivertissementC'est le sujet de tout ce qu'on consomme sans l'avoir fabriqué : regarder, écouter, jouer.
sève (Position.SEVE)C'est la position qui dit : cette sphère n'occupe aucun endroit de la structure, elle passe par tous.
TRONC (arbre_canon.TRONC)C'est la position centrale de la structure, celle où ce que les huit autres produisent est compilé et gardé ; S9 l'occupe.TRONC (libellé, face données de S0) — C'est le nom sous lequel S0 apparaît quand on la regarde du côté de ce qui est enregistré.face (enum Face) — Elle dit sous quel angle on nomme une sphère : le même objet reçoit un nom différent selon l'angle choisi.
TRONC (libellé, face données de S0)C'est le nom sous lequel S0 apparaît quand on la regarde du côté de ce qui est enregistré.TRONC (arbre_canon.TRONC) — C'est la position centrale de la structure, celle où ce que les huit autres produisent est compilé et gardé ; S9 l'occupe.
face (enum Face)Elle dit sous quel angle on nomme une sphère : le même objet reçoit un nom différent selon l'angle choisi.
Creation (sacred_name de S7)registre spirituelC'est le nom de registre sacré que porte la sphère de la formation.Création (S4) — C'est le sujet de ce qu'on fabrique soi-même.
canalC'est par quel moyen un message voyage : le réseau interne, le web, le courriel, le SMS, l'écran, et onze autres.
status (green / yellow / red)C'est la note de confiance mise sur l'étiquetage d'un message : vert, le classement est bon ; rouge, il a échoué.
bus (valeur de canal)C'est la valeur qui dit : ce message ne sort pas de la machine, il passe d'un module à l'autre dans le même programme.bus (l'organe de distribution) — C'est la pièce de code qui reçoit un message et le remet à tous ceux qui s'y sont abonnés.
bus (l'organe de distribution)C'est la pièce de code qui reçoit un message et le remet à tous ceux qui s'y sont abonnés.canal — C'est par quel moyen un message voyage : le réseau interne, le web, le courriel, le SMS, l'écran, et onze autres.bus (valeur de canal) — C'est la valeur qui dit : ce message ne sort pas de la machine, il passe d'un module à l'autre dans le même programme.
topicC'est le nom exact d'un message, celui qui décide qui le recevra ; il s'écrit en trois morceaux séparés par des points.
discipline_flagC'est une marque posée sur chaque message pour dire : ce contenu est soumis à la règle qui interdit d'affirmer plus que ce qui a été mesuré.
portée (VALID_PORTEES : indiv | commu | civ)C'est la taille de l'espace qu'un message concerne : une personne, un groupe, ou tout le monde.
checkpointUn arret qui bloque une action sensible jusqu'a ce qu'une personne identifiee dise explicitement oui, non ou annule — sinon l'action expire sans se faire.
fail-closeQuand l'outil ne peut pas faire le travail pour de vrai, il refuse et dit pourquoi, au lieu de repondre « c'est fait ».
porteUn passage qu'on traverse vraiment : il faut une permission pour le franchir, et il laisse une trace de ce qui est passe.porte (Porte de la Connaissance, knowledge_gate/porte.py) — Elle decide combien d'un document on montre a quelqu'un — de rien du tout jusqu'au texte complet — en six crans, et par defaut elle ne…
porte (Porte de la Connaissance, knowledge_gate/porte.py)Elle decide combien d'un document on montre a quelqu'un — de rien du tout jusqu'au texte complet — en six crans, et par defaut elle ne montre rien.porte — Un passage qu'on traverse vraiment : il faut une permission pour le franchir, et il laisse une trace de ce qui est passe.fenetre — Une vue sur ce qui se passe ailleurs : on voit, on ne prend rien, et rien ne change de main.
gardeUn controle pose sur le chemin d'une action, qui la laisse passer ou la refuse.
η (clamp 0.30) / MAX_INTERPRETIVE_CONFIDENCEUne limite de confiance : ce qui n'est pas un fait sourcé ne peut jamais sortir avec plus de trente pour cent de certitude annoncée.
Hz / fréquence (famille Solfeggio)registre spirituelUne courte liste de nombres admis : un nombre qui n'y figure pas est refusé comme hors famille.
dissonance (V)Un nombre qui dit de combien le sens d'un mot s'est éloigné de son sens d'origine dans un contexte donné.
Babel (Décodeur de Babel)L'outil qui mesure comment les mots ont été retournés, et qui refuse de répondre plutôt que de deviner quand sa base manque.
embeddingUne suite de nombres qui devrait rapprocher deux textes de sens voisin — mais, dans ce qui tourne aujourd'hui, elle est tirée d'un simple calcul d'empreinte du texte.

Matière : PLANCHES-MATIERE-3-DONNEES-2026-10-03.json (sha256 634d4e52ceb9) — planche de matière « L'AXE DES DONNÉES — par quoi une donnée est classée, et jusqu'où la classe tient ». Générée par scripts/generer-planches-ancrage.py ; la version « avant » (avant le passage du sceptique) que la matière conserve n’est pas dessinée — c’est la version dégradée au doute qui l’est, et les marges portent les dégradations.