L'AXE DES DONNÉES — par quoi une donnée est classée, et jusqu'où la classe tient
ouvrir la planche en plein écran → · 32 pièces · 20 traverses · 15 marges
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.
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
| 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/ |
| 1 | Registre 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). | |
| 2 | Les 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 | |
| 3 | arbre_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 | |
| 4 | Le 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). | |
| 5 | VALID_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 | |
| 6 | Pipeline 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 | |
| 7 | BABEL /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. | |
| 9 | T3 — 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. | |
| 11 | corpus_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. | |
| 12 | Ce 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. | |
| 13 | register_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`. | |
| 14 | core/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 ». | |
| 18 | routing_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. | |
| 20 | T2 — 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. | |
| 21 | Le 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. | |
| 22 | L'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. | |
| 32 | Corpus 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. |
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.
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.