Section 15
Tensions entre couches
Version refaite le 2026-07-20, mesurée contre le code. Remplace la §15 du 2026-04-27.
15.1 — Ce qui a changé depuis avril
La §15 d'avril documentait deux tensions. Voici où elles en sont.
Tension 1 — agent-hierarchy.js donne canApprove / canModifySystem / requiresHITL: false à L0_SYSTEM.
Réparée dans le navigateur, contredite par le serveur.
Dans frontend/js/agent-hierarchy.js:86-91, L0_SYSTEM porte aujourd'hui canApprove: false, canModifySystem: false, requiresHITL: true, maxImpactScore: 0.0. Le geste est daté dans le code (« Π.0 B1, 2026-04-24 ») et un veilleur a été ajouté aux lignes 803-824 pour crier si les valeurs repassent à l'envers. Sur ce point précis, la Charte d'avril décrivait une plaie déjà fermée.
Mais le geste n'a été posé que d'un côté. Le serveur porte encore la doctrine d'avant : backend/app/services/base_agent.py:82 dit AgentLevel.L0: 1.0, # Absolute authority, et le commentaire de la ligne 80 affirme « Impact thresholds from agent-hierarchy.js » — c'est-à-dire qu'il se réclame du fichier réparé pour justifier exactement le contraire de ce que ce fichier dit. Net : réparée à moitié, et la moitié non réparée cite la moitié réparée comme source.
Tension 2 — l'OPA autorise certains nova.execute sans checkpoint.
Encore là, et plus grave que décrit — mais pas dans le sens annoncé.
La règle existe (backend/opa/policies/governance.rego:122-134), elle est bien écrite, et elle n'a jamais été évaluée une seule fois. Trois ruptures indépendantes :
- Aucun serveur OPA n'existe. Zéro occurrence dans les quatre fichiers
infrastructure/compose/*.yml ; OPA_ENABLED=false ; le middleware n'est actif que si OPA_MIDDLEWARE_ENABLED=true, défaut faux.
- Même s'il montait, le code interroge
/v1/data/che_nu_v2/policies (backend/app/core/config.py:196) — un nom de dossier, pas un paquet. Le paquet réel s'appelle chenu.governance. Aucun des 23 paquets .rego du dépôt ne porte le nom interrogé.
- L'endpoint gardé n'existe pas.
POST /api/v1/nova/execute n'est déclaré dans aucun routeur ; le routeur nova est monté sur /api/v2/nova et n'a aucune route /execute. Le seul fichier qui traduisait une URL en opération « nova.execute » (backend/middleware/governance.py:56) ne s'importe même pas : il fait from config.settings import settings et backend/config/ n'existe pas.
Et le renversement, qui est le fait le plus important de toute cette section : ce qui protège nova aujourd'hui, c'est l'absence d'OPA. Quand OPA est injoignable, governance.py:112-120 renvoie reason="Governance service unavailable", nova.py:1421 compare cette chaîne exacte et bascule sur _local_governance_decision, qui exige un checkpoint dès que le mode est EXECUTION. Si un opérateur démarre OPA de bonne foi, la réponse arrive sur un chemin indéfini, reason vaut None, la comparaison de chaîne échoue, et nova retourne require_checkpoint=False — en mode EXECUTION compris. Réparer l'infrastructure enlèverait la garde, sans aucun message d'alerte.
Ce que la §15 d'avril ne voyait pas : elle documentait deux tensions. La mesure d'aujourd'hui en compte 60, dont vingt-deux critiques, réparties sur huit angles. Les deux d'avril n'étaient pas les pires. Elles étaient les seules qu'on avait regardées.
15.2 — Les tensions, classées par gravité
#### Rang 1 — Ce qui annule une loi haute depuis une couche basse
A. Un except Exception convertit un refus constitutionnel en réponse normale.<br>backend/app/routers/nova.py:694. La chaîne est complète et mesurable : foundation ou tree_laws lève une violation → nova_pipeline.py:960 pose GovernanceStatus.DENIED → nova_pipeline.py:1444-1454 retourne un résultat avec error_code="GOVERNANCE_DENIED" → la propriété success vaut False → nova.py:692 fait raise Exception("Pipeline returned no result, falling back to inline") → nova.py:694 l'attrape, journalise en warning, et exécute le chemin de secours _generate_suggestions(), qui n'importe ni foundation ni tree_laws. L'usager reçoit un 200 marqué COMPLETED, checkpoint_required: False.<br>Ce qui devrait primer : tree_laws.py:7-8 — « ABSOLUTE and IMMUTABLE. They override all other rules, configurations, and optimizations. No exception, no override, no bypass. »<br>Ce qu'un agent peut faire : tout. Sur la route Nova principale, aucune violation de loi n'a de conséquence visible. C'est littéralement le cas que la Charte interdit — sauf que ce n'est même pas une permission qui annule la loi, c'est un bloc de rattrapage d'erreur écrit pour la robustesse.
B. Le point d'entrée en flux ne traverse aucune loi.<br>backend/app/routers/nova.py:734. chat_with_nova_stream appelle _analyze_intent puis _check_governance, puis envoie directement payload.message au connecteur LLM (nova.py:811-822). nova_pipeline n'apparaît qu'aux lignes 662-678 du fichier, dans chat_with_nova seulement.<br>Ce qu'un usager peut faire : appeler /nova/chat/stream au lieu de /nova/chat et contourner intégralement le tronc éthique et les trois lois de l'Arbre. Ce n'est pas un contournement, c'est le fonctionnement nominal de l'endpoint.
C. Le 423 annoncé par les lois de l'Arbre n'existe pas — et violer une loi est le chemin le plus sûr pour l'éviter.<br>tree_laws.py:53 annonce « HTTP 423 Locked — any autonomous decision is blocked until human approval ». Le seul HTTP_423_LOCKED du parcours est nova_routes.py:306, dans un except CheckpointRequiredError — exception que seule la Lane E (checkpoint) lève, à nova_pipeline.py:1483. Or une violation de loi fait un return anticipé à nova_pipeline.py:1444-1454, avant que la Lane E ne commence à la ligne 1459.<br>Ce qu'un agent peut faire : déclencher une loi et n'obtenir ni verrou, ni checkpoint, ni mise en attente. Le Souverain n'est jamais consulté sur exactement les cas où la Charte promet qu'il le serait.
D. Le tronc éthique est branché sur un fil où rien d'interdit ne peut passer.<br>nova_pipeline.py:915 passe à foundation.py une chaîne construite comme f"{intent.intent_type.value}_{sphere_id}". IntentType est un enum fermé de 15 valeurs (query, search, create, delete, publish, transaction…). foundation.py ne fait que chercher ses 19 mots interdits dans cette chaîne, par sous-chaîne. Aucune des 15 étiquettes ne contient aucun des 19 mots — les 280 combinaisons ont été passées, 0 bloquée. master_mind.py:469 est pire : il passe la constante f"master_mind_output_{sphere}", 20 combinaisons, 0 bloquée. Le message brut de l'usager, lui, est bien envoyé à tree_laws (nova_pipeline.py:934) — jamais à foundation.<br>Ce qu'un usager peut faire : écrire à Nova n'importe quelle demande de profilage psychologique, de score comportemental ou de pilotage prédictif. Le tronc éthique reçoit « query_S1 » et laisse passer.
E. Les lois inspectent la prose humaine, pas les actions machine.<br>nova_pipeline.py:933 passe action=request.message[:200] — le texte tapé par l'usager. master_mind.py:479 passe action=result.message[:200] — le texte produit par le LLM. Or les mots-clés de violation (auto_delete, bypass_approval, dark_pattern, shadow_state) sont des identifiants techniques d'actions.<br>Ce que ça produit, dans les deux sens : une vraie suppression automatique ne s'annonce pas en écrivant « auto_delete » dans la conversation, donc elle passe. Et le Souverain qui écrit « explique-moi pourquoi le behavioral scoring est interdit chez nous » se fait refuser sa propre question par sa propre loi. La garde se déclenche exactement à l'envers de ce qu'elle protège.
F. Consulter le journal immuable l'efface et le réécrit.<br>backend/app/routers/hcs_audit.py:38 pose _hcs_audit_db_guard sur les six endpoints — tous des GET. Le finally de cette garde (lignes 31-32) appelle persist_hcs_audit_state, qui descend dans persist_stores (hcs_audit_persistence.py:308) et exécute DELETE FROM hcs_audit_records WHERE store_name = :store_name avant de réinsérer ce qui se trouve alors en mémoire vive. Après un redémarrage, la mémoire est vide.<br>Ce qu'un usager peut faire : consulter la preuve, et par ce seul geste la détruire. La table n'existe pas encore en production — les 257 migrations n'ont jamais tourné — donc le DELETE échoue et le mal ne se produit pas. Le jour où la migration passe, le défaut s'arme.
G. Le demandeur approuve son propre checkpoint, et la garde interdit qu'un tiers le fasse.<br>backend/app/routers/checkpoints.py:568-587. verify_checkpoint_access compare created_by au user_id de session et lève 403 « Not your checkpoint » sinon. approve_checkpoint (:745) et batch_approve (:844) passent tous deux par cette fonction. Aucune vérification de rôle, aucune séparation demandeur/approbateur nulle part.<br>Ce qu'un usager peut faire : détruire en deux appels HTTP au lieu d'un — recevoir le checkpoint_id dans le 423, POST /approve dessus, rejouer l'action. Et le Souverain qui voudrait approuver une destruction demandée par quelqu'un d'autre reçoit un 403.
#### Rang 2 — Ce qui bloque définitivement et pousse au contournement
H. Dix des quinze magasins de checkpoints n'ont aucun endroit où approuver.<br>13 PersistentDict distincts + 2 tables SQL (checkpoints et checkpoints_checkpoints). Cinq endpoints /approve existent en tout. Dans ocw.py, agents.py, oneclick_engine.py et layout_engine.py, la chaîne "approved" n'apparaît que dans des comparaisons, jamais dans une affectation. Dans community.py, entertainment.py et social.py, le magasin n'est même jamais relu.<br>Ce qu'un usager peut faire : rien. La suppression d'un espace de travail persistant, l'exécution d'un agent L3/L4, la suppression d'un flux sont bloquées à jamais. Il n'existe aucune URL au monde pour les débloquer. Ce ne sont pas des points de contrôle, ce sont des verrous soudés — et la seule façon de faire fonctionner un agent gaté devient d'enlever son drapeau.
I. Le 423 des agents est une porte sans poignée.<br>backend/app/routers/agents.py:758-792 — monté core=True (main.py:1226), c'est le chemin d'exécution d'agent réellement servi. Il crée un checkpoint « pending » dans PersistentDict("agents_checkpoints") et lève un 423 ; pour passer il faut cp["status"] == "approved". Aucune des 10 routes du fichier n'écrit ce statut. Le frontend appelle /api/v2/checkpoints/{id}/approve (checkpoint-manager.js:716), qui écrit dans un autre magasin et lève un 404 si l'identifiant n'y est pas — il n'y est jamais.<br>Ce qu'un usager peut faire : voir « une approbation humaine est requise », cliquer pour approuver, recevoir un 404, et n'avoir aucun recours. Les 53 agents porteurs de requires_human_gate sont inexécutables.
#### Rang 3 — Ce qui ment
J. Approuver un checkpoint Nova n'exécute rien et déclare le contraire.<br>backend/app/routers/nova.py:1063-1095. La route lit l'intention (ligne 1082), supprime l'entrée (1084), et retourne "message": "Action has been approved and executed." (1093). Entre les deux, il n'y a qu'un del et un logger.info. La variable intent ne sert qu'à composer la chaîne de réponse. En prime, _pending_requests (nova.py:558) est un dictionnaire Python nu, pas un PersistentDict : perdu au redémarrage, non partagé entre workers.<br>Ce qu'un usager peut faire : approuver une action sensible, lire que le système l'a exécutée, et ne pas réessayer. C'est pire qu'une absence d'approbation : c'est un faux témoignage de consentement effectif, avec sa trace dans les journaux.
K. L'audit des angles morts ne peut pas échouer — 15/15 par construction.<br>backend/app/routers/blindspot.py:29 n'accepte ni ne transmet de context, donc blindspot_audit.py:187 fait ctx = {} : les 10 contrôles capables d'échouer lisent tous ctx.get(...) et passent. Les 5 autres — dont CI-02 hidden_ranking et UD-01 engagement_optimization — n'ont aucun passed=False dans leur corps. En cas d'exception, le bloc 205-214 enregistre passed=True.<br>Ce que ça produit : un tableau de bord affichant 100 % de conformité éternelle, ce qui décourage précisément l'enquête qui trouverait tout le reste de cette section. Pire qu'absent : il rassure.
L. L0 veut dire quatre choses différentes dans le même dépôt.<br>agent-hierarchy.js:35 : superviseur système, zéro autonomie. base_agent.py:82 : autorité absolue, seuil 1.0. agents.py:245 : _canonical_agent_level retourne L0 en dernier recours, quand un agent n'a ni is_system_agent, ni requires_human_gate, ni capacité L1/L3 — L0 y est le plus démuni. agents.py:364 : L0 est le palier gratuit du marché. Et livre-reference.html:1350 annonce au public un « L0 Fondateur » qui n'existe dans aucun code, en citant un fichier de 548 lignes qui en compte 898.<br>Ce que ça produit : un agent sans aucune capacité tombe automatiquement dans le niveau que le navigateur décrit comme ayant canAccessAllData: true. Toute règle écrite « pour L0 » vise deux populations opposées selon le fichier qui la lit.
#### Rang 4 — Ce qui contredit son propre nom
M. « Enforce impact threshold » n'applique rien. base_agent.py:222-229 : output.requires_approval = True puis logger.info. Aucun raise, aucun return None. Et avec max_impact = 1.0 pour L0, la condition impact_score > 1.0 ne peut jamais être vraie pour un score normalisé — le drapeau n'est même pas posé.
M-bis. finalize() n'est pas un sceau. backend/audit/logs/immutable.py:193-196 pose self.finalized = True ; record() se termine ligne 161 par self.finalized = False. Écrire une entrée de plus rouvre un journal scellé et jette la racine précédente. AuditEvent n'est pas frozen (ligne 39) et self.events est une liste Python nue.
M-ter. Le mode silence est coupé en deux clés. ethic-point.js:180 teste ATOMAgentGuards.isSilenced — la fonction s'appelle isSilenceActive. Le test est toujours faux, le code retombe sur localStorage.getItem('atom_silence_mode'), alors qu'agent-guards.js:92 écrit dans atom_guards_silence. L'usager active le silence depuis le centre Nova ; les points d'éthique continuent de lui parler.
#### Rang 5 — Ce qui est ouvert par la porte d'à côté
N. Nova détruit sans point de contrôle, par la route que personne ne surveille. nova.py:1010-1037 : DELETE /conversations/{id} fait await db.delete(conv) puis commit(). Suppression dure, pas d'archivage, pas de deleted_at. La route n'appelle jamais _check_governance et n'est dans aucune table d'opérations sensibles.<br>Ce qu'un usager peut faire : effacer définitivement de la mémoire. Ce n'est pas nova.execute qui était le danger ; c'était la route que personne n'a pensé à surveiller parce que l'attention était sur celle qui n'existe pas.
O. Cinquante suppressions sur soixante-treize ne rencontrent aucun contrôle. Sur 73 @router.delete dans app/routers/, 50 ne mentionnent ni « checkpoint » ni 423. Le cas le plus parlant : governance.py:453, DELETE /circles/{id}/members/{user_id} — retire un membre d'un cercle de gouvernance sans checkpoint et sans vérifier qui appelle (caller_id est déclaré ligne 454 puis jamais lu).<br>Ce qu'un usager authentifié peut faire : expulser n'importe quel membre de n'importe quel cercle de gouvernance.
P. Le serveur vend un agent dont la capacité déclarée est « engagement_optimization ». backend/app/data/agents_fallback.py:211 — le terme banni mot pour mot. Ligne 264 : agent « Badges », leaderboards. Ligne 49 : streak_rewards, wellness_score. Ligne 73 : meditation_streaks. Ce fichier est servi au public par /marketplace et exécutable par POST /{agent_id}/execute, dont le corps (agents.py:660-720) vérifie call_agent et agent_chain, jamais agent.capabilities.
Q. Le régime enfant n'est chargé nulle part, et ne s'activerait pas s'il l'était. child-covenant-mode.js (462 lignes) n'apparaît dans aucune des 543 entrées de governance-init.js ni dans aucune des 122 pages HTML — sauf la-charte.html, où il est cité comme protection. Et même chargé : canActivate dépend de data.age !== undefined sur l'événement auth.user.registered, dont l'unique publieur (config.js:196) construit un payload de quatre champs où le mot « age » n'apparaît pas. Le second déclencheur, auth.user.age.verified, n'a aucun publieur dans tout le dépôt.<br>Ce qu'un mineur peut faire : localStorage.removeItem('atom_child_covenant'), rafraîchir, redevenir adulte. Et fabriquer un consentement parental en tapant onze caractères quelconques — _verifyParentSignature (ligne 209) se contente de consent.signature.length > 10.
R. Le consentement backend est une île. Un grep de check_consent|has_consent|verify_consent sur tout backend/app, hors privacy_router.py, retourne zéro ligne. Sur 355 routeurs, un seul sait ce qu'est un consentement. citizen_registry.py:418-437 stocke date de naissance, adresse, nationalité et langues, et le fichier entier ne contient aucune occurrence de « consent », « age » ni « mineur ».
15.3 — Les lois qui ne bloquent rien
Cette catégorie est la plus dangereuse, parce qu'une loi énoncée et non appliquée fait mieux que ne rien faire : elle décourage de vérifier. Elle est écrite, elle est citée, elle rassure — et elle ne referme rien.
| Loi énoncée | Où elle est écrite | Ce qui l'applique |
| Les 3 lois de l'Arbre sont absolues et sans exception | tree_laws.py:7-8 | 2 appelants sur 750 fichiers backend ; 2 routes sur 4317 ; aucun 423 |
| Le tronc éthique prime sur tout | foundation.py:26 | 2 fichiers l'importent, zéro routeur ; les 2 lui passent une chaîne où aucun interdit ne peut apparaître |
| L'autorité de décision humaine est exclusive | foundation.py:40-62 | branche morte — 4 mots-clés (take, choose, override, elevate) testés contre des chaînes qui ne les contiennent jamais ; orchestrator_may_not n'est lu par aucune ligne |
| Toute action IA est visible, traçable, auditable | tree_laws.py:74-84 | rien ne lit de journal ; le contrôle cherche le mot « unlogged_change » dans le texte de l'usager |
| Le décorateur d'audit de gouvernance | governance.py:838 | grep "@audit(" = 0 résultat ; le journal reçoit zéro événement |
| Le journal immuable HCS | hcs_audit_service.py (634 lignes, 6 méthodes d'écriture) | seuls appelants : backend/tests/unit/test_hcs_audit_db_contract.py |
| Le journal Merkle | backend/audit/logs/immutable.py | correct et vrai — mais son routeur REST importe MerkleNode, DailyAnchor, daily_anchors, merkle_tree, _finalized : 0 occurrence dans le module. Illisible sauf quand il est vide |
REQUIRE_CHECKPOINT_FOR = {data_delete, data_export, publish, external_action} | config.py:242 | aucun appelant de check_governance ne passe jamais ces actions ; les 5 appelants passent « mint » et « transfer ». Et si on les passait, governance.py:146 répondrait allowed=True |
| Les 10 capacités bannies aux agents | agent-guards.js:55-59 | canCallAgent ne lit jamais cette liste ; isForbiddenAction et checkAgainstPTC ont zéro appelant hors tests. 3 des 10 termes n'existent nulle part côté serveur |
| Les 7 défauts de vie privée | governance-constitution.json:133-141 | grep privacyDefaults = définition seule, zéro lecteur. Le vérificateur ne teste que sealed === true, or le JSON porte "sealed": false |
| Le droit à l'oubli | privacy-policy-engine.js:342 | écrit un objet status: 'pending', publie un événement sans abonné, retourne. Ne vide même pas la liste locale de consentements. Sa note dit « immediate and irreversible » |
| §9 dignité des données, §10 protection des enfants | Charte | zéro occurrence de `privacy | child | minor | consent dans foundation.py, tree_laws.py, constitution_engine.py, governance.rego` — les quatre couches supérieures |
FORBIDDEN_SORT_FIELDS | social.py:70-80 | recopié dans deux réponses JSON, jamais utilisé comme garde |
| La validation MasterMind (phase 7) | master_mind.py:463-489 | logger.warning puis return PhaseResult(success=True) en dur ; tree_laws_check et foundation_check sont écrits et lus par personne |
L'immuabilité de FOUNDATION | foundation.py:118-120 | frozen=True n'interdit que la réaffectation. Une ligne — FOUNDATION.collective_data_rules['min_cohort_size'] = 1 — supprime la règle de cohorte pour tout le processus. Vérifié en exécution |
15.4 — Ce qui tient vraiment
Tout n'est pas fissuré. Ce qui suit a été mesuré et fonctionne.
Le fail-close monétaire est réel. ur_engine.py:142 et :215 lisent decision.allowed et refusent le mint et le transfert quand la gouvernance ne répond pas, avec un commentaire qui documente le bug d'avant (« anciennement : except Exception → allowing mint »). Comme OPA est absent, check_governance retourne allowed=False : la monnaie est réellement bloquée en ce moment. Même chose pour hedera_routes.py:222-227, qui refuse l'egress avec un 423 si la gouvernance est injoignable.
treasury_service.py:921-926 est le seul appelant qui lit correctement les deux drapeaux — if not decision.allowed or decision.require_checkpoint. C'est la forme à copier partout ailleurs.
nova_pipeline.py ferme pour de vrai. Ligne 960 il pose DENIED, ligne 1444 ce statut est honoré et retourne GOVERNANCE_DENIED. C'est le seul endroit mesuré où une loi énoncée arrête réellement l'exécution avant le LLM. Le mécanisme d'arrêt existe et il est correct — le problème est le except qui le rattrape vingt lignes plus loin, et ce qu'on lui donne à inspecter.
L'interdiction agent-vers-agent est une vraie garde serveur. agents.py:681-699 lève un 400 dur si call_agent ou agent_chain est présent dans le corps. Non contournable depuis le navigateur.
threads.py:1018-1032 refuse la suppression d'événement avec un 405 sec — pas de checkpoint, pas de négociation. Append-only, point. Une loi qui ne se discute pas est une loi qui tient.
La seule garde exécutoire sur un mineur, dans tout le dépôt, existe. jeunesse_runtime/agent.py:462-466 lève ValueError("Parental consent required for registration"), le routeur est monté (main.py:1843), la ligne 470 anonymise l'identifiant et la 474 hache le profil. Réserve : parental_consent est un booléen que le client envoie lui-même.
agent_execution.py:86-118 et :307-354 est une implémentation propre et complète du gate — quatre conditions de déclenchement, checkpoint en base, approbation qui vérifie la propriété puis exécute. Le bon code existe déjà. Il n'est simplement pas sur le chemin monté en core.
agent_contracts.py:328-331 fait ce que la Charte annonce : une proposition sans requires_approval est signalée et corrigée d'office. Le défaut du champ est déjà True. Cette règle-là ne se contourne pas facilement.
Le Merkle de backend/audit/logs/immutable.py:71-130 est du vrai code de preuve. build(), get_proof(), verify_proof() sont cohérents, le nœud impair est bien dédoublé, le chaînage est propre. Personne ne s'y branche, mais le module n'est pas une façade.
decision-echo.js:161 lève « FAILSAFE VIOLATION » sur deleteEcho(). C'est la seule fonction de tout le domaine de l'audit qui refuse vraiment quelque chose. Sa protection ne couvre que l'usage nominal, mais l'intention est écrite et exécutoire — c'est le geste à imiter ailleurs.
Le veilleur d'anti-régression de agent-hierarchy.js:803-824 est une bonne idée honnête. Un développeur qui remettrait canApprove: true verrait une erreur rouge. Il ne regarde qu'une fois, au chargement, mais il protège contre le scénario le plus probable : la distraction.
15.5 — Les cinq réparations, par rendement
Classées par le rapport entre la taille du geste et la taille du trou refermé. Aucune des cinq n'est un chantier.
1. Ne plus rattraper un refus constitutionnel avec un except.<br>backend/app/routers/nova.py:692-694 — trois lignes. Avant le raise Exception générique, tester if pipeline_result and pipeline_result.error_code == "GOVERNANCE_DENIED": raise HTTPException(423, detail=pipeline_result.governance.violations). Le repli reste disponible pour les vraies pannes techniques et cesse de couvrir les violations de loi.<br>Rendement : c'est la plus petite du lot et la plus grande. Tout le travail déjà fait en amont — foundation, tree_laws, DENIED, le retour ligne 1444 — devient effectif d'un coup sur la route Nova principale. Aujourd'hui, corriger n'importe quoi d'autre en amont ne change rien tant que cette ligne existe.
2. Retirer la persistance du finally d'une garde de lecture.<br>backend/app/routers/hcs_audit.py:31-32 — supprimer deux lignes. Aucun des six endpoints n'écrit ; il n'y a rien à persister au retour d'un GET.<br>Rendement : c'est une désamorce. Le défaut ne se manifeste pas aujourd'hui uniquement parce que la table n'existe pas en production. Le jour où les migrations passent, consulter le journal d'audit l'efface. Faire ce geste maintenant coûte deux lignes ; le faire après coûtera l'historique.
3. Aligner le serveur sur la réparation d'avril.<br>backend/app/services/base_agent.py:82 → AgentLevel.L0: 0.0, plus réécriture des commentaires des lignes 73-74 et 80 pour qu'ils disent ce que le fichier dit vraiment.<br>Rendement : deux valeurs et trois commentaires. Ça ne débloque rien — le niveau n'est vérifié nulle part de toute façon — mais ça retire le seul texte du dépôt qui invite activement à re-régresser dans le mauvais sens, en se réclamant faussement du fichier corrigé. C'est la réparation qui empêche la prochaine tension d'être écrite.
4. Cesser de dire « exécutée ».<br>backend/app/routers/nova.py:1093 — une ligne. Tant que l'exécution n'est pas branchée : "message": "Approbation enregistrée. Exécution non encore implémentée."<br>Rendement : le trou reste, mais il cesse d'être invisible. Un système qui ne fait pas ce qu'il promet est réparable ; un système qui affirme l'avoir fait ne le sera jamais, parce que personne ne saura qu'il faut le réparer. Le mensonge coûte plus cher que le trou.
5. Donner aux deux lois ce qu'elles doivent inspecter.<br>nova_pipeline.py:916 et :933. À la 916, passer à foundation le texte réel — request.message[:200] — en plus de l'action synthétique. À la 933, passer à tree_laws le nom d'opération plutôt que la prose, en suivant le geste que son voisin immédiat de la ligne 916 fait déjà. Et ajouter intent_type au contexte de master_mind.py:480, sans quoi la clause Human Agency de tree_laws.py:133-141 ne peut littéralement pas s'allumer.<br>Rendement : trois clés de dictionnaire. Elles transforment deux gardes correctes mais aveugles en gardes qui voient — et elles corrigent le retournement où la loi refuse la question du Souverain pendant qu'elle laisse passer l'acte.
Suivantes, dans l'ordre : Object.freeze sur LEVEL_PERMISSIONS et MappingProxyType sur les deux dicts de FOUNDATION (l'immuabilité déclarée devient réelle) ; governance.py:146 → allowed=False sur la branche REQUIRE_CHECKPOINT_FOR (un checkpoint exigé n'est pas une autorisation) ; master_mind.py:487 → success=laws_result["passed"] ; un magasin de checkpoints unique au lieu de quinze ; et un premier test qui appelle POST /api/v2/nova/process avec une charge conçue pour violer une loi et affirme status_code == 423. Tant que ce test échoue, il dit la vérité sur l'état du système.
Aucun fichier de test du dépôt ne mentionne tree_laws. Recherche faite sur tous les .py hors archives/ : zéro. Les 60 tensions ci-dessus ont pu s'installer et durer sans qu'aucun signal ne se déclenche — non pas parce que la surveillance a échoué, mais parce qu'il n'y avait rien à surveiller. Une loi sans test est une loi dont personne ne saura jamais qu'elle a cessé de s'appliquer. Ni, comme ici, qu'elle n'a jamais commencé.
15.6 — Ce qui a été réparé, et ce que la reprise a corrigé d'elle-même
Ajouté le 2026-07-20, après application. Les tensions ci-dessus sont conservées telles qu'écrites : une section qu'on réécrit à mesure qu'on répare perd sa valeur de témoignage. Ce qui suit dit où elles en sont.
Trois affirmations de cette section étaient fausses, et il faut le dire avant le reste.
La §15.2-E soutenait que les lois inspectent la prose et manquent l'acte. C'est vrai pour foundation, faux pour tree_laws : nova_pipeline.py:932-939 lui passe bien intent_type en contexte, et la clause Human Agency (tree_laws.py:133-147) le lit. La garde voyait donc les deux. Le déséquilibre était unilatéral. Là où le reproche tient, c'est master_mind.py:480, dont le contexte n'a effectivement pas d'intent_type.
La §15.1 situait le fail-close dans backend/app/services/governance.py. Ce fichier n'existe pas — c'est backend/app/core/governance.py. Les numéros de ligne, eux, étaient bons.
La §15.2-J laissait entendre que d'autres routeurs affirment faussement une exécution. Balayage fait : sur tout app/routers/, seul nova.py le faisait. Les quatre autres candidats — government.py:882, griffe.py:925, my_team.py:887 — exécutent réellement avant de le dire.
La cause de fond, que ni cette section ni le premier examen n'avaient vue.
La clause Human Agency ne se déclenche que pour un appelant agent ou orchestrator ; son défaut est human. Or nova_pipeline.py:918 et :935 écrivaient "orchestrator" en dur, pour tout le monde. Un humain qui tapait « crée-moi une note » était donc étiqueté orchestrateur, et refusé au motif qu'un agent agissait sans approbation. La loi était juste ; l'étiquette était fausse.
Ce n'était donc pas un arbitrage entre CHECKPOINT_INTENTS et sensitive_intents — aucune des deux listes n'avait tort. NovaRequest porte désormais caller_type, et le seul appelant réellement autonome du dépôt — nova_3d_npc.py, qui fabrique ses propres prompts — pose "orchestrator" explicitement sur ses quatre sites. La loi garde exactement la population qu'elle vise.
Refermé le 2026-07-20 : le rattrapage du refus constitutionnel (15.2-A) — un GOVERNANCE_DENIED lève maintenant un 423 portant ses violations, ce qui fait exister sur /nova/chat le verrou que tree_laws.py:53 promet ; le renversement OPA (15.1) — GovernanceDecision.degraded rend le fail-close structurel, l'ancienne comparaison de chaîne étant gardée en OU ; allowed=True sur REQUIRE_CHECKPOINT_FOR ; l'expulsion libre des cercles de gouvernance (15.2-O) ; l'audit à 100 % éternel (15.2-K) ; la phase 7 de MasterMind, dont le verdict est calculé, lu, et désormais branché sur le résultat de la course ; le faux « approved and executed » (15.2-J) ; le désalignement L0 serveur/navigateur (15.2-L) ; le silence coupé en deux clés (15.2-M-ter) ; le régime enfant chargé nulle part (15.2-Q, chargement seulement) ; FORBIDDEN_SORT_FIELDS devenu une vraie garde ; les capacités bannies servies par le catalogue public (15.2-P) ; et l'immuabilité de façade, côté FOUNDATION comme côté LEVEL_PERMISSIONS.
Non réparé, volontairement, avec le motif :
- La suppression douce des conversations (15.2-N). Une conversation « supprimée » resterait lisible indéfiniment dans le déversoir admin, et le droit à l'effacement ne serait plus satisfait — cela contredirait la §9. Arbitrage produit, pas réparation.
- La désamorce du journal HCS (15.2-F). Un test verrouille contractuellement la persistance sur GET. Changer un contrat testé dépasse le geste minimal. À trancher, en sachant que le défaut s'arme le jour où les migrations passent.
- La gouvernance sur
/nova/chat/stream (15.2-B). C'est un chantier : la Lane D n'est pas appelable seule. Le geste juste tient en trois temps — extraire de GovernanceChecker.check une fonction pure qui n'appelle que foundation et tree_laws sans dépendre des objets du pipeline, l'appeler avant d'ouvrir le flux, puis la faire consommer par les deux routes. Tant que le premier temps n'est pas fait, tout raccourci dupliquerait la loi en deux exemplaires divergents.
- L'adresse OPA (
config.py:196, qui interroge un paquet inexistant). La corriger seule rendrait le serveur joignable avant que le fail-close ne tienne. L'ordre compte plus que le geste.
Deux défauts neufs, trouvés en réparant et non par l'audit :
CheckpointRequiredError ne peut pas être levée. Trois sites d'appel, trois signatures incompatibles avec sa définition (exceptions.py:185-214) : chacun lève un TypeError immédiatement rattrapé par un except Exception qui le convertit en PIPELINE_ERROR. Le except CheckpointRequiredError: raise de nova_pipeline.py:1539 ne s'exécute donc jamais.
La règle de cohorte (foundation.py:216) n'est atteignable que si l'action contient le mot anglais collective ou aggregate. Le dépôt communique en français : une demande d'agrégation rédigée normalement ne franchit jamais la condition, quelle que soit la taille de la cohorte.
Et le fait qui a changé : backend/tests/unit/test_tree_laws_and_foundation.py existe. Vingt-cinq assertions vertes, deux écarts marqués xfail avec leur motif. Un test de cohérence surveille désormais l'écart entre les deux listes et refuse de le laisser dériver en silence. Une loi sans test reste une loi dont personne ne saura qu'elle a cessé de s'appliquer — sur ces deux-là, ce n'est plus vrai.