L'ARMURE 15D — quinze fentes, une mesure, et la chaîne rompue avant le premier envoi
ouvrir la planche en plein écran → · 10 pièces · 22 traverses · 12 marges
L'armure n'est pas l'organe le plus durci du dépôt : c'est un organe DURCI MAIS DÉBRANCHÉ — son pont frontend→backend ne peut pas se déclencher (trois ruptures indépendantes), sa garde de signature n'a aucun appelant, son régime v2 keyé n'existe qu'en test, et son unique sortie vers l'économie se déclare elle-même inactive ; ce qui tient vraiment, c'est le refus : le registre fail-close et la contrainte de base qui interdit à l'armure d'être autoritative.
Ce que cette collecte N'A PAS pu voir. (1) AUCUNE BASE, AUCUNE PROD : lecture seule, par consigne. Donc NON ÉTABLI — que la migration v82_293 soit appliquée sur le VPS (CLAUDE.md mesure la prod au 2026-08-08 à `v82_260_atom_positions`, soit 33 révisions en retard de la table d'observations d'armure) ; que la table `armure_owner_observation_versions` contienne ne serait-ce qu'une ligne ; qu'un `identities.id` existe pour un usager authentifié (la FK `fk_armure_observation_owner` restreint sur `identities`, et un usager absent de cette table ferait échouer l'insertion en 503 — c'est la même famille de défaut que le `owner_principal_id` corrigé au commit dfcb8a3ec, mais je ne l'ai PAS vérifiée). (2) AUCUN SERVEUR LANCÉ, AUCUN NAVIGATEUR : je n'ai pas chargé une page, donc l'enchaînement « module chargé → bus → POST » est établi par LECTURE DE CODE, pas par observation. Un script inline d'une page HTML particulière pourrait appeler `ATOMArmureBridge.pushDimensions` ou `updatePoint0` d'une façon que mes motifs de recherche ont manquée — j'ai cherché `ATOMArmureBridge`, `pushDimensions`, `refinePoint0`, `updatePoint0`, `.aspirate(`, `.breathe(` et `FractalExpansion` sur frontend/ et agents/, et n'ai trouvé que des tests ; je n'ai pas lu les 122 pages HTML une à une. (3) AUCUN TEST LANCÉ : je n'ai exécuté aucune garde, aucun banc. Toute mention de test dans ce rendu décrit un FICHIER LU, jamais une sortie verte. (4) PÉRIMÈTRE DES « ZÉRO APPELANT » : chaque « 0 appelant » de ce rendu a été mesuré par `git grep` sur l'ARBRE SUIVI PAR GIT, au commit 8addf1c94, en excluant `backend/tests/`, `**/__tests__/**`, `docs/`, `leaves/`, `leaf-flows/` et `frontend/etat-technique.html` — et en INCLUANT les deux arbres de `backend/` (`backend/app/`, 1 627 fichiers, et les ~278 `.py` hors `app/` sur 29 racines), plus `frontend/`, `agents/`, `hf-atom-tools/`, `scripts/`. `archives/` et `transit/` sont exclus comme code de référence non actif. (5) LE COMMIT, PAS L'ARBRE : tous les chiffres et toutes les lignes citées viennent de `git show HEAD:<fichier>`. L'arbre de travail porte 60+ fichiers modifiés non commités (dont `main.py`) : une mesure sur l'arbre pourrait différer. (6) LES 12 `backend/verticals/` ACTIFS sont des bytecode-squatters non versionnés, donc invisibles à `git grep` : si un appelant d'armure y vit, je ne l'ai pas vu. (7) LES BUNDLES CODEX ET LES WORKTREES hors dépôt n'ont pas été fouillés.
ne pas dessiner
| nft-soul-manager.js (mint) alimente | → | FractalExpansion.aspirate()FractalExpansion.js:882 exige `data.tokenId` ; nft-soul-manager.js:375-381 publie `{serial, userId, stage}` ; antenna.py:570 lève 503 inconditionnel (l.53-65) | |
| meta-map-manager.updatePoint0() alimente | → | FractalExpansion.aspirate()meta-map-manager.js:289,314 ; 0 appelant de production de updatePoint0 (seul appel : __tests__/unit/canon-messagebus-wiring.test.js:246) | |
| FractalExpansion.js alimente | → | armure-bridge.jsFractalExpansion.js:173 (seul publieur de `fractal.agent.aspirated`) ; armure-bridge.js:191 (abonné) — l'abonnement est bâti, l'émission impossible | |
| aura-display.js alimente | → | armure-bridge.js (D5, la seule mesure réelle)aura-display.js:344 publie `aura.updated` ; armure-bridge.js:193-196 met en cache ; mais la valeur ne sort qu'avec pushDimensions, jamais appelé | |
| armure-bridge.js alimente | → | POST /api/v2/armure/dimensionsarmure-bridge.js:121,126-160 ; module chargé governance-init.js:392 ; routeur monté main.py:1957 — code des deux côtés bâti, déclencheur jamais atteint | |
| routeur armure produit | → | armure_15d.compute_armure_snapshot (v1 nu)backend/app/routers/armure.py:93 (key=None → armure_15d.py:155-156) | |
| armure_15d v2 (HMAC keyé + aveuglement) produit | → | tout appelant de productionarmure_15d.py:121-162 ; 0 appel keyé hors backend/tests/test_atom_pay.py:532-599 | |
| armure_15d.snapshot_matches autorise | → | armure_sentinel.guardarmure_sentinel.py:136 — le seul appelant existe, mais guard lui-même n'est appelé par personne | |
| armure_sentinel.guard gouverne | → | runtime (toute route, tout service)0 appelant dans le dépôt hors tests ; armure.py n'appelle que armure_fingerprint (l.94) et expose_threat (l.142) | |
| routeur armure alimente | → | armure_owner_observation_registryarmure.py:99-118 → registry:343 (arecord_owner_observation), commit avant retour registry:588 | |
| registre produit | → | armure_trajectory.extend_chain / genesis_linkregistry:32,470,472 | |
| registre (chaîne écrite) gouverne | → | armure_trajectory.verify_chainarmure_trajectory.py:91 ; 0 appelant de production dans tout le dépôt | |
| base de données gouverne | → | classe de preuve de l'armuremodels/armure_owner_observation.py:90-94 (CHECK evidence_class='owner_submitted') ; migration v82_293 | |
| routeur armure produit | → | immune.threat.detectedarmure.py:129-150 ; conditionné à guard_verdict=='threat', inatteignable sans première observation ; best-effort sous except (l.148-150) | |
| immune.threat.detected alimente | → | QGEngineConnector (survival/deduction/incidents)qg_engine_connector.py:98-102 ; abonnement effectué au démarrage main.py:604-606 | |
| armure_store autorise | → | aip_signing (signature AIP)aip_signing.py:155,174-180 ; 0 écrivain de armure_store → refus systématique ArmureSnapshotUnavailable | |
| armure_store alimente | → | verite.py (cote d'identité de l'enquête)verite.py:731-741 → identity_provenance(armure_present=False) pour tout usager | |
| armure_store autorise | → | passage_decision.ArmureStorePortpassage_decision.py:1228-1235 — provenance=MEMOIRE même garni ; identite_ancre() rend id() d'un dict | |
| dimensions_15.py (schéma canon) gouverne | → | antenna.py::DIMENSIONS_15antenna.py:154-170 recopie à l'identique ; 0 import de dimensions_15 dans antenna.py | |
| dimensions_15.py (schéma canon) gouverne | → | spiderweb.py::DIMENSIONS_15spiderweb.py:103-107 — 15 autres noms, 0 nom commun | |
| dimensions_15.py (schéma canon) gouverne | → | frontend/js/dimensional-armor.js::DIMENSIONSdimensional-armor.js:49-64 — troisième vocabulaire, module CHARGÉ (governance-init.js:587) | |
| armure alimente | → | économie (UR, trésorerie, forge)armor-economy-bridge.js:6-11 se déclare inactif et n'est dans aucun tableau de scripts ; D7/D9 (dimensions_15.py:32,34) sont des étiquettes, pas des soldes |
| 1 | Le registre durable des observations d'armure625 lignes ; verrou `pg_advisory_xact_lock` par propriétaire (registry:385-394) ; rejeu par nonce avec DEUX refus distincts (corps vs effet dérivé, l.429-437) ; commit AVANT tout retour (l.588) ; 503 nommé `armure_observation_persistence_unavailable` si indisponible (armure.py:110-115, 182-187) ; 10 contraintes CHECK sur la table des versions ; migration dans la chaîne (v82_292 → v82_293 → v82_294)backend/app/services/armure_owner_observation_registry.py (625 L) ; backend/app/models/armure_owner_observation.py ; backend/alembic/versions/v82_293_armure_owner_observations.py · confirmé dans le codeBÂTI et fail-close — c'est la meilleure pièce du domaine, et je la confirme. MAIS elle n'a AUCUN PRODUCTEUR VIVANT : son unique entrée est POST /api/v2/armure/dimensions, dont la pièce « pont » établit qu'aucun client chargé ne l'appelle. Le registre est atteignable par un appel authentifié direct, pas par le système livré. Divergence doc↔code à noter : la genèse de chaîne est ancrée sur `owner_id` (registry:470, `genesis_link(str(owner))`) alors qu'armure_trajectory.py:41 annonce une ancre sur l'âme (`nft_soul_id`). | |
| 2 | ⚑ LA BASE INTERDIT À L'ARMURE D'ÊTRE AUTORITATIVE1 contrainte CHECK qui fige DEUX colonnes : canonicalization_schema = 'atom.armure.owner-observation.v1' ET evidence_class = 'owner_submitted' ; `authoritative: False` écrit EN DUR dans les deux réponses du routeurbackend/app/models/armure_owner_observation.py:90-94 (ck_armure_observation_non_authoritative_class) ; même CHECK dans la migration v82_293 ; routeur backend/app/routers/armure.py:159 et :205 · confirmé dans le codeRIEN sur le plan du code — c'est la pièce la plus honnête du domaine et je la confirme sans la dégrader. Elle rend structurellement impossible qu'une observation déposée par son propriétaire devienne une attestation. Un seul caveat de PORTÉE, pas d'état : la contrainte vit dans la migration v82_293, et CLAUDE.md mesure la prod au 2026-08-08 à la révision `v82_260_atom_positions`. Je n'ai interrogé aucune base (lecture seule, aucune prod) : que la table existe en production est NON ÉTABLI. | |
| 3 | Le schéma canonique des 15 dimensions15 dimensions = 5 triades × 3 (len(DIMENSIONS_15)==15, index 1..15 contigus, garde is_valid_schema() l.73) — RE-VÉRIFIÉ au commit. MAIS 4 emplacements portent une table « 15 dimensions », pour 3 vocabulaires incompatiblesbackend/app/services/dimensions_15.py:25-44 ; importé par armure_15d.py:209 et fractal_dimensions.py:29 · confirmé dans le codeLa prétention de « source unique » (en-tête l.11-15) est fausse au commit : antenna.py:154-170 RECOPIE la table à l'identique sans jamais l'importer (0 import de dimensions_15 dans antenna.py) ; spiderweb.py:103-107 porte 15 autres noms (Physical, Emotional… Cosmic) ; frontend/js/dimensional-armor.js:49-64 un troisième (Électrique, Physique, Réseau…) — et ce dernier EST chargé en production (governance-init.js:587). Zéro nom commun entre les trois vocabulaires. « Dimension 7 » désigne financial, Financial et Décision selon l'organe. | |
| 4 | L'engagement cryptographique — ce que les docs appellent « l'encryption »1 appelant de production de `compute_armure_snapshot`, avec key=None → régime v1 SHA-256 NU. Régime v2 (HMAC-SHA256 domaine-séparé + aveuglement) : 0 appelant de production dans tout le dépôt ; 30+ appels, tous dans backend/tests/test_atom_pay.py:532-599backend/app/services/armure_15d.py:37,121-162 ; appelé une seule fois en production à backend/app/routers/armure.py:93 · confirmé dans le codeTrois choses. (1) Le seul régime VIVANT est le v1, que le module lui-même qualifie de « TRAÇABLE — qui connaît les ids publics recompose le hash » et « NON recommandé » (l.125-128) : le routeur appelle `compute_armure_snapshot(bindings)` sans clé. (2) Le v2 n'a ni porteur de clé, ni custody, ni rotation — rien ne lit ni ne fabrique une clé d'armure dans le dépôt ; `new_blinding()` (l.75) et `stratified_snapshot()` (l.178) ont ZÉRO appelant hors tests. (3) Ce n'est PAS un chiffrement : rien n'est déchiffrable, c'est un engagement (hash) ; le module l'avoue (l.137-141 : « CE QUE v2 N'ATTEINT PAS : prouver-sans-révéler »). Dérive mineure mais réelle : `_ARMURE_VERSION = "15d.v1"` (l.37) entre dans le canonique y compris en régime v2 — le payload scellé dit « v1 » dans les deux régimes. | |
| 5 | La trajectoire chaînéeÉCRITE : `previous_chain_digest` + `chain_digest`, NOT NULL, CHECK hex64. VÉRIFIÉE : `verify_chain` = 0 appelant dans tout le dépôt hors backend/tests/ et hors archives/ (les occurrences d'`archives/backend-reference/` sont d'autres fonctions, code de référence non actif). `next_step` et `step_from_store` : 0 appelant de productionbackend/app/services/armure_trajectory.py:40-47,91-113 ; écrite par registry:470-472 ; colonnes models/armure_owner_observation.py:53-54 + CHECK l.83-89 · confirmé dans le codeLa chaîne est écrite durablement, elle n'est JAMAIS RELUE. `verify_chain` (armure_trajectory.py:91) — la fonction qui dirait « maillon rompu » ou « maillon forgé », c'est-à-dire exactement « la discontinuité de la personne » que le fichier donne pour sa raison d'être (l.11-19) — n'est appelée par aucune route, aucun service, aucun ordonnanceur. Aucun endpoint ne rend une trajectoire. Donc : durable, oui ; vérifiable, non ; vérifiée, jamais. | |
| 6 | L'exposition de menace1 consommateur réellement abonné au démarrage (`QGEngineConnector.get_instance().connect_bus()`, main.py:605) routant `immune.threat.detected` vers 3 moteurs (survival/deduction/incidents) ; 1 producteur, conditionné à `guard_verdict == "threat"`producteur backend/app/routers/armure.py:129-150 ; organe backend/app/services/armure_sentinel.py:148-180 ; consommateur backend/app/services/qg_engine_connector.py:98-102, abonné au démarrage via backend/app/main.py:604-606 · confirmé dans le codeLe CONSOMMATEUR est bien monté — je confirme cette moitié. Le PRODUCTEUR est inatteignable : `guard_verdict` ne vaut « threat » qu'après une observation PRÉCÉDENTE puis une perte/substitution du noyau (armure_sentinel.py:78-83) ; avec zéro producteur de pont, il n'y a jamais de première observation, donc jamais de seconde, donc jamais de verdict de menace. Deuxième réserve, avouée dans le code lui-même (armure.py:130-133) : l'exposition est best-effort sous `except Exception` (l.148-150) — aucune outbox, donc une menace perdue après le commit n'est jamais réémise. | |
| 7 | ⚑ Le pont JS→Python des dimensions vivantes0 publication de `fractal.agent.aspirated` possible en production ; 3 ruptures indépendantes ; 0 appelant externe de `aspirate()` / `breathe()` dans tout le dépôt git hors tests (arbres fouillés : frontend/, agents/, backend/app/, backend/ hors app/, hf-atom-tools/, scripts/)frontend/js/FractalExpansion.js:145,173,881-884,893-899 ; frontend/js/nft-soul-manager.js:352-357,375-381 ; frontend/js/meta-map-manager.js:289,314 ; backend/app/routers/antenna.py:53-65,567-570 · confirmé dans le codeLES DEUX CÔTÉS SONT BÂTIS ET CHARGÉS — armure-bridge.js (governance-init.js:392, sur les 28 pages de GOVERNANCE_PAGES l.50-66), FractalExpansion.js (l.359), routeur monté (main.py:1957) — mais l'ÉVÉNEMENT QUI LES RELIE NE PART JAMAIS. Seul publieur de `fractal.agent.aspirated` = FractalExpansion.js:173, dans `aspirate()` (l.145). `aspirate()` n'a AUCUN appelant externe ; ses deux seules entrées sont des abonnements, et les deux sont rompues : (a) `nft.soul.minted` — FractalExpansion.js:882 exige `data.tokenId`, or le seul publieur (nft-soul-manager.js:375-381) envoie `{serial, userId, stage}`, SANS `tokenId` → la garde n'est jamais vraie ; et de toute façon le mint ne réussit jamais, antenna.py:570 appelle `_antenna_nft_mutation_not_active()` qui lève 503 INCONDITIONNELLEMENT (l.53-65) et nft-soul-manager.js:352-357 re-lève sans repli ; (b) `metamap.point0.updated` — seul publieur meta-map-manager.js:314, à l'intérieur de `updatePoint0()` (l.289), qui a ZÉRO appelant de production : le seul appel du dépôt est un test unitaire (frontend/js/__tests__/unit/canon-messagebus-wiring.test.js:246). Conséquence : 0 POST /api/v2/armure/dimensions depuis le frontend livré. La seule mesure RÉELLE du système (cohérence d'aura → D5) arrive bien dans le cache du pont (armure-bridge.js:193-196, publiée par aura-display.js:344, module chargé l.928) et n'en sort jamais. | |
| 8 | ⚑ La garde de signature — l'organe qui VÉRIFIE un hash revendiqué`snapshot_matches` : 1 appelant, armure_sentinel.py:136. `armure_sentinel.guard()` : 0 appelant dans tout le dépôt git hors tests (arbres fouillés : backend/app/, backend/ hors app/ (29 racines), hf-atom-tools/, scripts/, agents/, frontend/)backend/app/services/armure_15d.py:222-237 (snapshot_matches) ; backend/app/services/armure_sentinel.py:6-8,123-145 (guard) · confirmé dans le codeL'en-tête d'armure_sentinel.py:6-8 annonce : « `snapshot_matches` n'avait AUCUN appel runtime — l'armure ne gardait rien. Ici la garde devient vivante. » C'EST FAUX AU COMMIT : le fichier a été écrit, mais `guard()` n'est appelé par personne. Le routeur n'appelle que `armure_fingerprint` (armure.py:94) et `expose_threat` (l.142) — jamais `guard`. Donc aucun chemin runtime ne compare jamais une armure PRÉSENTÉE au miroir vivant ; ce qui est câblé (classify_observation, registry:478) compare des cardinalités et deux digests déjà stockés, jamais une revendication d'un porteur. ⚑ Trois fiches de conseil du dépôt affirment le contraire — data/urbanisme/projets-services/protocol-999-protocole-de-resistance/conseil_3.json:89, conseil_6.json:54 et conseil_9.json:58 écrivent « `armure_sentinel.guard` appelée depuis `armure.py:96-100` », et conseil_3 la déclare « le seul espace de ce lieu à la fois bâti, appelé et monté ». L'appel n'existe pas. | |
| 9 | ⚑ Le magasin que la signature lit, et que personne n'écrit`set_latest` : 0 appelant dans tout le dépôt git hors tests. 3 lecteurs de production. `sign_aip_payload` : 0 appelant de production (re-vérifié au commit, hors backend/tests/)backend/app/services/armure_store.py:8-12,45 ; lecteurs : backend/app/services/aip_signing.py:146-188, backend/app/routers/verite.py:731-741, backend/app/services/passage_decision.py:1215-1243 · confirmé dans le codeLe store est un `Dict` de processus plafonné à 5 000 membres, et PERSONNE NE L'ÉCRIT — le fichier le dit lui-même (l.8-12 : « le routeur de production écrit dans armure_owner_observation_registry, jamais ici […] Aucun câblage de production ne doit le repeupler »). Trois organes continuent pourtant de le LIRE, et chacun en tire une conclusion permanente : (1) aip_signing.py:174-180 → `ArmureSnapshotUnavailable` à chaque signature (fail-close volontaire et bien nommé, mais c'est un refus systématique, pas une garde) ; (2) verite.py:732-741 → `identity_provenance(armure_present=False)` POUR TOUT LE MONDE : la cote d'identité de l'enquête Vérité est structurellement « armure absente » ; (3) passage_decision.py:1228-1235 → `ArmureStorePort` déclare `provenance=MEMOIRE` même garni, et son `identite_ancre()` rend l'`id()` d'un dictionnaire Python. Le store a été correctement déclassé ; ses trois lecteurs n'ont pas suivi. | |
| 10 | La sortie de l'armure vers l'économie0 occurrence de `'/js/armor-economy-bridge.js'` dans le tableau `scripts` de governance-init.js ; 1 seule page du dépôt le mentionne, frontend/etat-technique.html (une page de constat, pas un porteur)frontend/js/armor-economy-bridge.js:6-11 ; absent des tableaux de scripts de frontend/js/governance-init.js (seule occurrence : un motif de TIER3 l.1318, qui classe un ordre de chargement et ne charge rien) · confirmé dans le codeLe fichier se déclare lui-même mort (l.6-11 : « Statut : inactif. Cette implementation attribuait localement des UR à partir de l'apparence et de la progression, sans registre financier durable […] l'armure ne crée ni solde, ni droit économique, ni redistribution »), et il n'est de toute façon chargé par aucune page. Le reste du fichier décrit encore trois flux de revenus et des multiplicateurs ×1.0 à ×5.0 : c'est de la DOCTRINE RÉSIDUELLE dans un module mort — à ne pas lire comme une mécanique. Il n'existe aucune arête bâtie entre l'armure et l'économie : D7 `financial` et D9 `security` (dimensions_15.py:32,34) sont des ÉTIQUETTES de catégorie, ni soldes, ni clés, ni preuves. |
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-2-INSTITUTIONNEL-2026-10-03.json (sha256 5cc49a404485) — planche de matière « L'ARMURE 15D — quinze fentes, une mesure, et la chaîne rompue avant le premier envoi ». 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.