# Activity Log · 2026-08-03 · Claude Code DTP Worker ## Session 180728 · Audit directive-vs-implémentation 4e couple (DIRECTIVE_PIE → pie/manifest) → CLEAN 0 édition · byte-repro + attributions vérifiées SÉMANTIQUEMENT · 1 SIGNAL Michel (downstream `contrats` gated→confotur ≠ générateur Promesa/Fideicomiso/HOA) **Contexte + choix de tâche.** `./run_ci.sh` au démarrage : **32 PASS · 0 FAIL · 0 SKIP**, arbre propre. Roadmap `ROADMAP_8_WEEKS_OR_LESS.md` : 8 sprints livrés (couverture CI = 32 suites). Surfaces de dérive usuelles saturées (re-scan = redondant #5). J'ai poursuivi la classe [[directive-vs-implementation]] sur son **4e couple** encore neuf : `DIRECTIVE_PIE_PROJECT_IDENTITY_ENGINE_20260803.md` ↔ `05_deliverables_mvp/pie/manifest/` (gaté par `pie-manifest-tests`) — après FINANCEMENT, MOBILE_STORES et V10. **Méthode.** Lecture intégrale de la directive PIE (181 l.) + `pie_manifest_gen.py` + `pie_spec.json` + `pielib/deps.py`. Confrontation champ-par-champ directive → spec, puis **byte-repro** (`build -o /tmp` == `out/` → BYTE-IDENTICAL). Contrairement à un audit de comptes, j'ai poussé jusqu'à la **fidélité SÉMANTIQUE** des attributions `module`, parce que l'invariant 6 ne vérifie QUE l'existence du répertoire (`deps.module_exists`) — un module *existant mais mal attribué* passe le gate en silence. **Fidélité directive → spec = totale (0 dérive gatée).** - 12 groupes Master Data == §16-29 (clés canoniques, inv 2). ✅ - 4 règles sync == §82-86 verbatim (prix/typologie/amenities/livraison → downstream exacts, inv 3-4). ✅ - 10 étapes workflow == §108-133 (contiguës, inv 7). ✅ - 9 marques P01-P09 == §90-102 (codes ⊆ CLAUDE.md §Projets, inv 8 ; noms de marque sourcés de la directive, couche distincte des code-names — déjà [[project-brand-vs-codename-not-drift]]). ✅ - `annexe 12` + `V10.1` verbatim dans la directive §170 (inv 9). ✅ - `storage_layout.racine` = `/opt/oto/data/pie/{code}/` hors-repo (inv 10 · #8). ✅ **Attributions `module` vérifiées SÉMANTIQUEMENT (au-delà de l'inv 6 dir-existence).** Les 4 downstream marqués `gated` pointent des répertoires réels ET — pour 3 d'entre eux — le module produit EXACTEMENT le livrable nommé : - `site → publiciste` : docstring publiciste = « Maintient le site public **vente.otov7.com** à partir des faisabilités » == libellé downstream « Page projet vente.otov7.com/projets/{slug}/ ». **Exact.** ✅ - `kit_banquier → faisabilite/bancable` : bancable = kit banquier/portail bancable ([[bancable-out-not-committed]]). **Exact.** ✅ - `app → mobile/app_config` : config app Expo, section portail Client. **Exact.** ✅ **⚠️ SIGNAL à Michel (attribution sémantiquement LARGE — à SURFACER, pas d'édition).** Le **4e** downstream, `contrats` (libellé spec « Contrats types (Promesa · Fideicomiso · HOA) », = directive §51), est marqué `statut: gated · module: legal/confotur`. Or `legal/confotur` produit le **DocType `CONFOTUR Application` (Ley 158-01, incitation touristique)** — pas les contrats Promesa / Fideicomiso adhésion / HOA. Grep sur tout `05_deliverables_mvp/` : **AUCUN module ne génère Promesa/Fideicomiso/HOA** (0 hit hors mention « fideicomiso » comme concept de financement dans `financement_spec.json`). Donc marquer `contrats` **gated** sur-affirme la couverture : le générateur de ces contrats reste en réalité **à_construire** (un item MVP P-niveau), gaté au **module légal le plus proche** (confotur) plutôt qu'à son producteur réel. C'est l'unique attribution PIE dont l'output ≠ le libellé downstream (les 3 autres sont exacts). **Pourquoi ne PAS éditer.** (1) `pie_spec.json` est la **source d'autorité** du module (spec=authority, [[directive-vs-implementation]]) ; la directive datée est un snapshot d'intention, **aucun gate directive↔spec (#5)**. (2) L'inv 6 gate honnêtement l'existence du répertoire (par design documenté) ; l'inv 5 lie mécaniquement `statut`↔présence-module — le **choix éditorial** du module attribué est ce qui porte le risque sémantique, et confotur est un **rattachement légal-famille défendable** (sa docstring se déclare « cross-cohérent avec les contrats déjà livrés » et référence `dossier_vente`/`workflow_vente`). (3) Trancher si `contrats` est « gated (famille légale) » ou « à_construire (générateur dédié absent) » est un **arbitrage de périmètre produit** pour Michel, pas une correction factuelle — même posture que les signaux mobile (bundle id) et V10 (couche wag/audit). **Résultat : 0 dérive gatée · 0 édition (prod/doc/gate) · byte-repro vert.** Classe [[directive-vs-implementation]] étendue au **4e couple** (PIE) = **CLEAN** + **1 signal de périmètre** surfacé. Vérification neuve : attributions `module` d'un manifest de dépendances confrontées à l'**output réel** des modules cibles (pas seulement dir-existence) — 3 exactes, 1 large. `run_ci.sh` = 32 PASS. Zéro module créé · zéro gate ajouté (#5) · zéro chiffre inventé (#6) · aucune commande VPS (#8) · aucun `git clean` . ## Session 173726 · Audit directive-vs-implémentation 3e couple + triangulation banques (V10 ↔ FINANCEMENT ↔ module) → 1 SIGNAL Michel · 0 édition prod **Contexte + choix de tâche.** `./run_ci.sh` au démarrage : **32 PASS · 0 FAIL · 0 SKIP**, arbre propre. Roadmap `ROADMAP_8_WEEKS_OR_LESS.md` : les 8 sprints ont leur module livré (couverture CI = 32 suites). Les surfaces de dérive usuelles (docstring-vs-CODE, comptes gatés, inventaire-vs-FS, constantes de marque #4, noms d'entités/projets) sont **saturées** (re-scan = redondant #5). J'ai ouvert la surface **directive-vs-implémentation** encore partiellement neuve : le **3e couple** `DIRECTIVE_WORKFLOW_FAISABILITE_V10` (annexe méta- constitution EAOF) — après `MOBILE_STORES` et `FINANCEMENT_BANCAIRE`. **V10 = snapshot méta-constitution, largement hors périmètre module.** La directive V10 est une **formalisation EAOF** (P0-P4 : créer `EAOF_DIRECTIVE_V10_FINAL.docx` sur le VPS, migrer V7→V10, Bureau Virtuel 12 directions, Stage Gates G0-G8, Annexes 1-11). Ces concepts (8 phases, 9 gates, 12 directions) **ne vivent dans aucun module in-repo** — ce sont des artefacts constitution VPS. Rien à gater (directive = snapshot daté comme `daily_reports` ; spec/module = autorité — classe `directive-vs-implementation`). **Triangulation banques V10 ↔ FINANCEMENT ↔ module — CLEAN.** V10 §5 liste **6 banques** (Banreservas · Popular · BHD · Santa Cruz · Scotiabank · López de Haro). Ensemble **identique** à `DIRECTIVE_FINANCEMENT` (noms complets : Banco Popular Dominicano · BHD León · Banco Santa Cruz · Scotiabank República Dominicana · López de Haro) et à `financement_spec.json` (6 `banques[].nom`, mêmes noms complets). Aucune banque manquante/inventée/mal-nommée. **SIGNAL Michel — gate condition #4 : le module encode une couche de directive SUPERSÉDÉE.** Découverte réelle en croisant les **3 couches internes** de `DIRECTIVE_FINANCEMENT_BANCAIRE_COMPLET` : - **Couche 1 (corps §Backend gate check)** puis **Couche 2 (AMENDEMENT « dépôt initial »)** : la 4e condition du gate = « **Un conseiller WAG a validé** (`wag_validated_by NOT NULL`) ». - **Couche 3 (PRÉCISION · Michel · plus tardive, l. 206-300)** : titrée « **La 4ème condition N'EST PAS un humain WAG** ». Michel écrit noir sur blanc « Contrairement à la version précédente qui mentionnait *conseiller WAG*, la validation est faite **directement par OTO Auditeur Finances**, un agent IA autonome. Aucun humain WAG n'est requis. » Et §« Gate check condition 4 mise à jour » : « Au lieu de `wag_validated_by IS NOT NULL`, la condition devient `condition_4_ok = audit and audit['decision']=='APPROVED' and audit['signature_valid']` ». - **Module** : `finlib/gate.py::_cond_validation_wag` implémente **exactement la couche 2 supersédée** — `bool(dossier.get("wag_validated_by"))`, message user-facing « Validation WAG manquante (aucun conseiller référent n'a validé) ». Sémantique **verrouillée** sur tout le module : code + tests (`test_validation_wag_manquante_bloque`) + README (table cond. #4) + artefacts byte-gatés `out/gate_spec.json` & `out/gate_status_example.json` (clé `validation_wag`). - **Zéro wiring auditeur in-repo** : `grep -i auditeur` sur le module = 0 hit ; la capability OTOIA `auditeur_finances` (8 vérifs : cohérence/apport/docs/signatures/OFAC/PEP/ratio<40%/ Fideicomiso/CONFOTUR + rapport JSON signé HMAC) est un **agent séparé hors module**. **Pourquoi SURFACE et pas édition (0 édition prod).** Cohérent avec la classe `directive-vs-implementation` (spec = autorité ; directive = snapshot ; SURFACE ≠ gate ≠ fix) : (1) migrer la condition #4 vers `audit.decision=='APPROVED'` **casserait le byte-repro** de 2 artefacts gatés + réécrirait tests/README ; (2) requiert l'agent `auditeur_finances` **inexistant in-repo** (capability VPS/OTOIA, Sprint 5-6) ; (3) c'est une **décision produit Michel** — le module garde-t-il un champ humain WAG (fallback « dernier recours » que la PRÉCISION conserve pour OFAC/PEP/recours) ou bascule-t-il la condition #4 sur la décision de l'agent IA ? La session `financement` précédente (2e701f2, CLEAN) avait vu l'Auditeur comme « agent séparé » mais **n'avait pas relevé que la PRÉCISION redéfinit la condition #4 du module lui-même** — c'est le delta neuf de cette session. Signal porté à Michel (ci-dessous + `daily_reports/2026-08-03.md`). `run_ci.sh` : **32 PASS** inchangé. ## Session 153722 · Audit cross-ref NEUF (noms d'entités + noms de projets vs CLAUDE.md) → CLEAN 0 édition · faux-positif « marque PIE ≠ code-name §Projets » caractérisé (2 couches gatées, par design) + tension P01/P09 re-signalée à Michel **Contexte + choix de tâche.** `./run_ci.sh` au démarrage : **32 PASS · 0 FAIL · 0 SKIP**, arbre propre. `daily_reports/2026-08-03.md` déjà courant (addendum `100714` · 24 modules · 32 PASS). Les ~6 sessions précédentes (`150719`→`130717`) sont **CLEAN 0-édition** : les surfaces de dérive usuelles (docstring-vs-CODE, comptes gatés, inventaire-vs-FS, constantes de marque #4) sont **saturées** — les re-scanner serait redondant (#5). J'ai donc ouvert une surface **INTER-RÉFÉRENCE NEUVE**, jamais balayée : la cohérence des **noms d'entités** (CLAUDE.md §Entités) et des **noms de projets** (§Projets) recopiés à travers les modules. **Surface 1 · noms d'entités — CLEAN.** WAF/WA SRL/AC Arias Cuevas/Consortium ECR DR/ Helios RD (sous WAG)/Ploutos/9060 QC : aucune entité mal-nommée, mal-rattachée (holding, paymaster) ni inventée dans `05_deliverables_mvp/**` ou `03_agents/**`. **Surface 2 · noms de projets — apparent « drift » P01/P09, en fait FAUX-POSITIF (élaboration légitime · 2 couches distinctes, toutes deux gatées).** Un balayage naïf oppose CLAUDE.md §Projets (« **P01 Structure** », « **P09 1069 Crisfer** ») à `pie/manifest/pie_spec.json` (« P01 → **Coralis** », « P09 → **Résidence Gazcue** »). Investigation → **deux couches complémentaires, pas une contradiction** : - **CODE-NAME** (nom interne §Projets) — `crm/dossier_vente/doctype_spec.json:24` (options du Select ERPNext) + `seo/fixtures/projets_master.json` utilisent fidèlement « P01 Structure »/ « 1069 Crisfer ». **Gatée** à la constitution par `check_readme_claims.sh:5187` (options DocType ancrées mot-pour-mot à §Projets). - **MARQUE** (nom public / brand) — `pie_spec.json` champ **`"marque"`** transcrit **verbatim** la liste « Architecture Multi-Marques » de `DIRECTIVE_PIE_…20260803.md:90-102` (Michel, aujourd'hui). Le gate PIE **invariant 8** (`pie_manifest_gen.py:144`) n'ancre que les **`brands[].code`** ⊆ §Projets — **jamais** le champ `marque`, par design. Le README:41 est déjà précis : « 9 projets, **codes** ancrés à `CLAUDE.md §Projets` ». - Cohérence d'ensemble : pour **7/9** projets, marque ≈ nom §Projets ; seuls **P01** (Structure ↔ Coralis) et **P09** (1069 Crisfer ↔ Résidence Gazcue) divergent substantiellement — un nom interne + une marque publique, exactement le rôle d'un *Project Identity Engine*. **Verdict : 0 dérive · 0 édition.** Le champ `marque` du PIE **n'est PAS** une dérive de §Projets — c'est la couche brand, sourcée de la directive PIE (SSOT #6) et byte-gatée ; les codes seuls sont ancrés à la constitution, et le README l'affirme déjà correctement. Classe « variante = élaboration légitime, pas dérive » ([[project-brand-vs-codename-not-drift]]). Ne pas éditer CLAUDE.md (constitution · [[otov7-platform-not-drift]]) ni le `pie_spec` (fidèle à sa source). **Zéro nouveau gate (#5)** — les deux couches sont déjà couvertes. **⚠️ Re-signalé à Michel (awareness, non tranché).** §Projets donne les noms internes « P01 **Structure** » / « P09 **1069 Crisfer** » tandis que la directive PIE (+ DESIGN_SYSTEM_v1, RENDUS, FINANCEMENT — tous root-owned, non éditables par le worker) porte les marques publiques « Coralis » / « Résidence Gazcue ». Rien à corriger côté worker (couches distinctes, gatées) ; seule décision ouverte : **CLAUDE.md §Projets gagnerait à cross-référencer la marque publique** (ex. « P01 Structure *(marque : Coralis)* ») pour lever l'ambiguïté d'un lecteur croisant les deux docs. Documenté ici (canal ungated), pas masqué, pas tranché unilatéralement. **Vérifications.** `./run_ci.sh` → **32 PASS · 0 FAIL · 0 SKIP** (inchangé — audit pur, aucun fichier de prod/gate touché). `git status` = 1 fichier (ce log). **Zéro nouveau module · zéro gate ajouté (#5)** — surface neuve vérifiée CLEAN + caractérisée pour éviter tout re-flag futur. Aucune commande touchant au VPS (#8) · aucun `git clean` . ## Session 083713 · Gate `constraints-guard` RED réparé — nouveau doc REFERENCE `OTO_DESIGN_SYSTEM_v1.md` (mention VPS, pas usage) + inconsistance chemin re-signalée à Michel **Contexte.** `./run_ci.sh` au démarrage : **29 PASS · 1 FAIL** — régression NEUVE (`constraints-guard`) apparue avec le commit `9c062ef` (« REFERENCE · OTO Design System v1 »), qui a ajouté `OTO_DESIGN_SYSTEM_v1.md` à la racine. Toutes les autres dimensions vertes ; arbre propre. **Cause racine (faux-positif · classe identique à la session 010630).** Le §15 du doc « **Fichiers canoniques sur VPS** » liste, en **inventaire** descriptif, deux emplacements `/var/www/html/static/` (assets concierge + scripts UI canoniques). Le garde (dont l'en-tête déclare « détecte l'**USAGE**… PAS sa simple **mention** ») a mordu ces deux lignes — mais ce sont des **mentions** d'emplacements *servis* par nginx (`/var/www/html/static` = **SYMLINK** de `/opt/oto/sites/static`, cf. CLAUDE.md §VPS), pas une **écriture exécutable**. L'usage réel vivrait dans un `.sh`/`.py`/`.yml` — toujours scannés. **Pourquoi PAS d'édition du doc fautif.** (1) Root-owned `root:root 644` — **non éditable** par `otoclaude` (`-w` négatif) → l'escape `ci-allow` par ligne est **inapplicable**. (2) Document **ajouté par un commit REFERENCE que je n'ai pas créé** → principe « surface, don't overwrite ». Exactement la situation de la session 010630 (fichiers root-owned de Michel). **Correctif — au niveau du garde que je possède (`ci/`), 2+ solutions pesées.** - *Option retenue* : ajout de `OTO_DESIGN_SYSTEM_*.md` (glob → survit à un futur `_v2`) aux exclusions de `tracked_files()`, **même catégorie** que `AUTORISATIONS_*.md`/`DIRECTIVE_*.md` (docs racine énumérant *nécessairement* des chemins VPS en tant que mention). En-tête du garde + `ci/README.md` §exclusions mis en cohérence (raison + inventaire-vs-usage documentés). - *Option écartée* : demander à Michel de rendre le doc éditable pour y poser des `ci-allow` — plus lourd, casse à chaque re-push du doc ; le fix dans MON `ci/` est durable. **Détection vraie-positive PRÉSERVÉE (mutation-test, [[guard-constraints-flags-usage-not-mention]]).** Un `.sh` temporaire commité avec les 3 usages littéraux (écriture chemin statique VPS, URL GitHub, appel Stripe) → le garde **mord les 3** (✗). Seule la prose de référence/gouvernance `.md` est exclue ; `CLAUDE.md` (constitution) **reste scanné**. Aucun trou vers un usage forbidden dans du code. **⚠️ INCONSISTANCE RE-SIGNALÉE À MICHEL (non tranchée par le garde).** Le §15 liste le chemin **servi** `/var/www/html/static/` alors que le mandat (CLAUDE.md · Interdits absolus + §VPS) préfère la **source** `/opt/oto/sites/static/` (le premier étant le symlink du second). Même inconsistance chemin-servi-vs-source que la l.35 d'`AUTORISATIONS_*.md` déjà signalée le 010630. Documentée ici + `ci/README.md`, **pas masquée, pas tranchée** (décision archi → Michel). **Vérifications.** `./run_ci.sh` → **30 PASS · 0 FAIL · 0 SKIP** (rétabli). Mes propres mentions du chemin dans `ci/README.md` (description de l'exclusion) marquées `ci-allow` (motif l.100 préexistant). **Zéro nouveau module · zéro gate ajouté (#5)** — le garde n'est pas affaibli (mutation 3/3 RED sur usage code), corrigé d'un faux-positif conforme à sa propre philosophie. Aucune commande touchant au VPS (#8) · aucun `git clean` . ## Session 063711 · Sprint 5/8 · Canal 2 `daily_reports/` remis à jour (3 sessions manquantes depuis 040643) **Contexte + choix de tâche.** `./run_ci.sh` au démarrage : **30 PASS · 0 FAIL · 0 SKIP**, arbre propre. Roadmap 1→8 intégralement livrée + gatée verte. Un sweep de dérive dédié (agent Explore : globs de sortie README-vs-`os.path.join`, docstrings générateurs, comptes prose, liens Markdown) a rendu **0 dérive ungated** — les gates existants (`check_readme_claims`/`check_artifacts`/`check_regression`/`check_docs`) couvrent l'acquis, et re-scanner serait redondant (#5). La valeur restante réelle in-périmètre : la **currency du canal 2 stakeholder** (contrat « Rapports quotidiens »), classe [[two-logging-channels]]. **Défaut trouvé (dérive de couverture, réelle).** Le snapshot `daily_reports/2026-08-03.md` s'arrêtait à l'addendum session `040643`. **Trois** sessions net postérieures manquaient au snapshot — toutes des correctifs d'intégrité doc (aucun ne bouge les chiffres launch-readiness, d'où table de clôture inchangée) : - `050654` — `ci/README.md` complété (2e workflow `mobile-build.yml`, commit `1af5d01`) ; - `053701` — fix prose-fact `regression_run.json` doc-vs-réalité (commits `89eb8e6` + `3d8ccb0`) ; - `060704` — fix citation roadmap fantôme module bancable (commit `88d9955`). **Réalisé.** Trois addenda concis insérés **avant** la table « fin de journée » (qui reste la synthèse de clôture), 100 % sourcés : chaque addendum cite son/ses commit(s), sa classe de dérive et le consommateur régénéré le cas échéant. Aucune valeur inventée. **Chiffres re-dérivés d'artefacts (aucun saisi à la main).** `regression_run.json.totals` = 22 suites · **569 ran / 569 passed · 0/0/0** ; `quality_report.json` verdict **PASS · 22 modules · min 100** (seuil 95) ; `acceptance_matrix.json` **15/15 `status=in_repo`** (`all_modules_gated` + `all_artifacts_exist` all true) ; README Mobile **4931 octets** (`wc -c`). Tous concordants → table de clôture laissée **inchangée** (correcte telle quelle). **Sûreté du changement.** `daily_reports/` n'est ni un artefact `out/` byte-gaté ni une source de claims de `check_readme_claims` (surfaces gatées = README/AGENT.md, pas les rapports quotidiens) → édition **gate-neutre**. Aucun claim numérique gaté touché · aucun terme interdit · aucune logique de prod ni de gate modifiée. **Vérifications.** `./run_ci.sh` → **30 PASS · 0 FAIL · 0 SKIP** (inchangé) · `git status` = 1 fichier. **Zéro nouveau module · zéro gate ajouté (#5).** Aucune commande touchant au VPS (#8) · aucun `git clean` . ## Session 033642 · Sprint 5 P1 · Livrable FONCTIONNEL : workflow CI/CD Mobile `.gitea/workflows/mobile-build.yml` (build EAS iOS + Android) **Contexte + choix de tâche.** `./run_ci.sh` au démarrage : **30 PASS · 0 FAIL · 0 SKIP**, arbre propre. Michel : « commits fonctionnels, pas juste docs ». La roadmap est couverte par des modules gatés+verts, mais `DIRECTIVE_MOBILE_STORES_20260803.md` (Sprint 5, actif) liste explicitement en **P1 · CI/CD Mobile** la création de `.gitea/workflows/mobile-build.yml` — classée **non-bloqueur** (« configurer sans token, activation quand Michel fournit »), donc **in-périmètre worker** (fichier config versionné, aucune exécution VPS/stores #8). Vérifié absent : `.gitea/workflows/` ne contenait que `ci.yml`. **Vraie tâche fonctionnelle non faite** → exécutée. **Livrable créé : `.gitea/workflows/mobile-build.yml`.** - **Triggers** (conformes directive P1) : push `main` + tags `v*` (paths `mobile/**` + le workflow) + `workflow_dispatch` (inputs `platform` all/ios/android, `profile`). - **Jobs** : `preflight` (TOUJOURS vert · prouve la présence de la config EAS versionnée `mobile/app_config/out/eas_build.json` + rapporte l'état des secrets, sans jamais les logger) ; `build-ios` (App Store #32) + `build-android` (Play Store) → `eas build --platform … --profile production --non-interactive` (directive P4). - **Source de vérité** : les profils de build viennent de l'artefact versionné `eas_build.json` (development/preview/production) — pas réinventés. - **Code natif hors repo** : le code Expo vit dans un **autre dépôt Gitea** (`/opt/oto/mobile/native/` · Expo 51→cible 54) ; cloné au run depuis la variable `vars.MOBILE_NATIVE_REPO` — **jamais recopié ici**. **Sûreté (activation différée · #6 zéro invention · #8).** Les jobs de build sont gardés par `if: secrets.EAS_TOKEN != '' && vars.MOBILE_NATIVE_REPO != ''` → **SKIP** tant que Michel n'a pas fourni token+repo (« Vrais bloqueurs » de la directive : EAS_TOKEN 2 min sur expo.dev). Conséquence : ce workflow **ne rend JAMAIS le CI rouge** avant activation, et `preflight` reste vert et informatif. Aucun credential fabriqué : token, repo natif, appleId/ascAppId/serviceAccount restent des secrets/vars `a_confirmer` — cohérent avec les champs `null · a_confirmer` du livrable mobile. Plateforme **Gitea Actions uniquement** (#2). **Non-régression des gates (vérifié).** `check_ci_integrity.sh` n'inspecte QUE `ci.yml` (chemin en dur `CI=".gitea/workflows/ci.yml"`) → le 2e workflow lui est invisible, aucun job à ajouter à `gate.needs`. `guard_constraints.sh` vert (aucune URL/usage interdit ; `actions/checkout@v4` déjà toléré via `ci.yml`). YAML valide (pyyaml). **Consommateur régénéré (classe [[artifact-reproducibility-gate]]).** Ajout d'un pointeur concis (paragraphe « CI/CD Mobile » + 1 ligne au tableau périmètre) dans `mobile/app_config/README.md` → sa taille passe 4261→4917 octets, mesurée par le critère **DOC** de l'audit 4Big. `check-artifacts` a viré ROUGE (dérive attendue) → `audit_4big_gen.py build -o out` régénéré : **seule** la ligne `evidence "README.md (…octets)"` change ; verdict inchangé **PASS · 22/22 modules ≥ 95**. Lien Markdown vérifié résolvant (`../../../.gitea/workflows/mobile-build.yml`). **Vérifications finales.** `./run_ci.sh` → **30 PASS · 0 FAIL · 0 SKIP**. **Zéro nouveau module · zéro gate ajouté (#5)** · aucune commande VPS (#8). Aucun `git clean` exécuté . ## Session 020634 · Dérive ground-truth résiduelle : roadmap L18 (colonne « Existe déjà ») affirmait encore « Expo 54 » — corrigée → Expo 51 réel (#6) **Contexte.** `./run_ci.sh` au démarrage : **30 PASS · 0 FAIL · 0 SKIP**, arbre propre. Focus mandat courant = Sprint 5 Mobile (`DIRECTIVE_MOBILE_STORES_20260803.md`). Le module in-repo `mobile/app_config` est complet et vert (schéma + 12 invariants · comptes 5 onglets / 44 rôles / 3 langues / 13 `a_confirmer` vérifiés contre `out/MANIFEST.json` · docstring/README cohérents). Les actions P1-P5 de la directive visent l'app réelle `/opt/oto/mobile/native/` (**hors repo #8**, bloquées sur EAS_TOKEN/credentials Michel). J'ai donc cherché une **dérive réelle in-repo** dans le périmètre worker. **Défaut RÉEL trouvé (prolonge la session 013634).** La session 013634 a corrigé la fausse affirmation « Expo 54 **en place** » dans `AGENTS_EXISTING_ASSETS.md §8` + la fiche `03_agents/mobile/AGENT.md`, en distinguant **runtime ACTUEL (51)** vs **cible de rebuild S5 (54)**. Elle a **délibérément laissé la roadmap** — mais son raisonnement ne visait que la **ligne 56** (Sprint 5 « **Rebuild Expo 54** » = la cible, décision P0 de Michel, à ne pas trancher). Elle n'a **pas examiné la ligne 18** : la table « Gains d'accélération identifiés », colonne « **Existe déjà** », qui disait `modules RBAC + API + Expo 54`. Or « Existe déjà » = ce qui **existe déjà** (à refactorer) : y écrire « Expo 54 » ré-affirme **exactement** la dérive « Expo 54 en place » corrigée partout ailleurs — le runtime réel vérifié (`package.json`, session 013634 + directive audit 2026-08-03) est **Expo 51**, et 54 est la **cible de rebuild** (colonne « Reste à faire » / Sprint 5 L56). **Contradiction interne prouvée.** `03_agents/mobile/AGENT.md:88` **cite** cette ligne : « État courant (**table roadmap L18**) : modules RBAC + API + **Expo 51** en place ». La fiche affirmait donc que la L18 dit « Expo 51 » alors que la L18 disait « Expo 54 » — une **citation en désaccord avec sa source**, introduite quand 013634 a corrigé la fiche sans corriger la roadmap. Grep confirme : l'unique consommateur de la L18 est `AGENT.md:88` ; le spec/MANIFEST mobile ancrent sur **l.56** (`roadmap_ref`/`expo_sdk_source`), jamais l.18. **Correctif — chirurgical (1 ligne), fidèle, non-gaté vérifié AVANT édition.** Roadmap L18 : `Existe déjà: … Expo 54` → `… Expo 51` (runtime réel), et `Reste à faire: Builds + submit stores` → `Rebuild vers Expo 54 + builds + submit stores` (rend la ligne honnête sur le travail restant, fidèle à Sprint 5 L56). Résout la contradiction avec `AGENT.md:88` (qui devient exact). **Ligne 56 « Rebuild Expo 54 » NON touchée** = la cible S5 / décision P0 de Michel, non tranchée. **Sûreté (vérifiée AVANT édition).** Le gate Expo-SDK (`check_readme_claims.sh:7779-7794`, ancrage roadmap INV-mobile) ancre via `re.search(r"Rebuild Expo (\d+)", roadmap)` → ne lit **que** la ligne 56 ; le gate (d) ne scanne que `mobile/app_config/README.md`, jamais la roadmap. La L18 était donc **ungated** pour ce « 54 ». Formulation « Rebuild **vers** Expo 54 » choisie **exprès** pour ne PAS matcher le bigramme `Rebuild Expo` → l'ancre du gate reste liée à la **L56** (pas de second littéral porteur, cf. `[[roadmap-anchor-gate]]`). **Vérifications.** `./run_ci.sh` → **30 PASS · 0 FAIL · 0 SKIP** (inchangé) · `grep 'Rebuild Expo' roadmap` = **1 seule occurrence, L56** (ancre intacte) · `git diff --stat` = 1 fichier, 1 insertion / 1 suppression. **Zéro nouveau module · zéro gate ajouté (#5) · aucune logique de prod ni de gate touchée** — pure correction de dérive ground-truth sourcée, prolongeant [[mobile-runtime-actual-vs-rebuild-target]]. Aucune commande touchant au VPS (#8) · aucun `git clean` . ## Session 013634 · Dérive ground-truth réelle : app mobile documentée « Expo 54 en place » alors que le runtime réel est **Expo 51** (upgrade 54 abandonné) — corrigée (#6) **Contexte.** `./run_ci.sh` au démarrage : **30 PASS · 0 FAIL · 0 SKIP**, arbre propre. La directive la plus récente (commit `85d2fa3`, aujourd'hui) — `DIRECTIVE_MOBILE_STORES_20260803.md` — porte le Sprint 5 Mobile. J'ai cherché la prochaine tâche prioritaire **non-complétée** qui soit **dans le périmètre worker**. **Périmètre : l'essentiel de la directive Mobile est bloqué / hors-repo (#8).** Le module de config mobile in-repo (`05_deliverables_mvp/mobile/app_config/`) est **complet et vert** (schéma + 12 invariants · Expo SDK 54 = **cible de rebuild** correctement ancrée à la roadmap S5). Les actions P1 (`mobile-build.yml` EAS), P2-P5 (features / assets binaires / submit stores) visent l'app réelle `/opt/oto/mobile/native/` (**hors de ce dépôt**) et sont **bloquées** sur EAS_TOKEN + credentials Apple/Google (attendus de Michel). Rien à construire ici. **Défaut RÉEL trouvé (investigation read-only, aucune commande VPS).** La directive elle-même (§« État actuel · audit 2026-08-03 ») déclare le runtime **Expo SDK 51.0.0**, l'upgrade Expo 54 **abandonné le 2026-07-27**. Vérification indépendante du `package.json` réel : `expo ~51.0.0` · `react 18.2.0` · `react-native 0.74.5` · `expo-router ~3.5.0` — et **même le backup `native.bak-upgrade54-*` est resté sur ~51** (le bump 54 n'a jamais atterri). Or l'inventaire d'actifs `AGENTS_EXISTING_ASSETS.md §8:63` affirmait « **Expo SDK 54 + React 19.1.0 + React Native 0.81.5 (upgrade 2026-07)** » comme runtime **en place**, et la fiche `03_agents/mobile/AGENT.md` recopiait ces versions (« Expo 54 en place »). C'est **exactement la classe interdite** par CLAUDE.md (« Documenter code sans vérifier existence courante ») + **#6** (« zéro invention · toujours vérifier sources ») : un actif documenté comme existant qui ne correspond pas à l'existence réelle. Un agent Mobile s'y fiant construirait sur une prémisse fausse (Expo 54 déjà en place). **Correctif — chirurgical, sourcé, distinguant ACTUEL vs CIBLE-REBUILD.** 1. `AGENTS_EXISTING_ASSETS.md §8:63` : runtime **actuel** corrigé en Expo 51.0.0 / React 18.2.0 / RN 0.74.5 / expo-router ~3.5.0 (vérifié `package.json`), avec note « upgrade 54 abandonné 2026-07-27 · cible rebuild = 54 (roadmap S5) ». 2. `03_agents/mobile/AGENT.md` : 5 mentions de runtime-courant « Expo 54 / React 19.1.0 / RN 0.81.5 » corrigées en Expo 51 réel, **en préservant** la cible de rebuild Sprint 5 = Expo 54 (titre, rôle, scope, table modules, état courant). **Ce que je n'ai PAS touché (délibérément).** (a) La roadmap S5 « **Rebuild Expo 54** » = la **cible** du rebuild, décision de Michel (directive P0 : « décision upgrade 51→54 » explicitement ouverte) — je ne tranche pas. (b) Le module `mobile/app_config` (`expo_sdk_major: 54`) = la config **écrite pour ce rebuild-cible**, byte-gatée et ancrée à la roadmap par `check_readme_claims.sh:7739` (artefact⇔MANIFEST⇔spec⇔roadmap⇔ README, gate (d) rejette tout « Expo N≠54 » dans son README) — **correcte telle quelle**, non touchée. La distinction clé : **runtime ACTUEL (51)** ≠ **cible de rebuild S5 (54)** ; l'inventaire d'actifs doit dire le réel, le module de config dit la cible. Aucune contradiction — ils décrivent deux choses différentes. **⚠️ SIGNAL À MICHEL (décision P0, non tranchée par moi).** La roadmap prescrit « Rebuild Expo 54 » mais le runtime réel est Expo 51 et l'upgrade 54 a été abandonné 2026-07-27 sans atterrir (backup inclus). La directive P0 demande justement cette décision (reprendre 54, ou rester sur 51 LTS). Tant que la roadmap n'est pas mise à jour par Michel, le module de config reste — correctement — la cible 54. Signal documenté ici (canal ungated), pas masqué, pas tranché unilatéralement. **Sûreté (vérifiée AVANT édition).** Les littéraux de version `0.81.5`/`19.1.0`/ `18.2.0`/`0.74` n'apparaissent dans **aucun** gate (grep) ; `check_readme_claims.sh` ne pinne ces fichiers que sur (l.239) un **compte d'agents** et (l.8071) l'**IP VPS + noms Docker** — jamais la version Expo ; le gate Expo-SDK (l.7739) ne scanne que `mobile/app_config/README.md`. Les mentions corrigées étaient donc **ungated** → édition sûre, zéro gate affaibli. **Vérifications.** `./run_ci.sh` → **30 PASS · 0 FAIL · 0 SKIP** (inchangé). `git status` = 2 fichiers (`AGENTS_EXISTING_ASSETS.md` + `03_agents/mobile/AGENT.md`). **Zéro nouveau module · zéro gate ajouté (#5) · aucune logique de prod ni de gate touchée** — pure correction de dérive ground-truth sourcée. Aucune commande touchant au VPS (#8) · aucun `git clean` . ## Session 010630 · Gate `constraints-guard` RED réparé (mandat signé Michel l.35) + contradiction inter-mandats signalée **Contexte.** `./run_ci.sh` au démarrage : **29 PASS · 1 FAIL** — régression NEUVE du gate depuis les commits de Michel de ce soir (`7e4456e`, `94ae143`, `9067313` ont introduit 4 fichiers root-owned à la racine). Job en échec : `constraints-guard`. **Cause racine.** `AUTORISATIONS_MICHEL_20260803.md:35` « - Écriture dans `/var/www/html/static/` (assets publics) » — une **autorisation positive** (parmi 40 permissions filesystem) qui matche le terme interdit `/var/www/html/static/` **sans** marqueur de prohibition (contrairement à la l.96 `❌ git clean` qui passe) ni `ci-allow`. Les 3 autres fichiers (`DIRECTIVE_*`) sont propres. C'est **exactement la classe de faux-positif** que le garde prétend ignorer (en-tête : « détecte l'USAGE… PAS sa simple mention ») — une mention en prose de gouvernance, pas un usage dans du code exécutable. **Pourquoi PAS d'édition du fichier fautif.** (1) Root-owned `root:root 644` — **non éditable** par `otoclaude` (test `-w` négatif). (2) C'est un **document signé par Michel que je n'ai pas créé** → principe « surface, don't overwrite ». La convention `ci-allow` par ligne était donc inapplicable. **Correctif — au niveau du garde (que je possède, `ci/`).** Ajout de `AUTORISATIONS_*.md` et `DIRECTIVE_*.md` aux exclusions de `tracked_files()`, **même catégorie** que les exclusions préexistantes (`ci/guard_constraints.sh` lui-même, `.gitea/workflows/*.yml`) : documents contenant *nécessairement* les termes interdits en tant que **texte de politique**. Robustesse durable : survit à un re-push du doc par Michel (le fix vit dans MON `ci/`, pas dans le doc root). **Détection vraie-positive PRÉSERVÉE (mutation-test).** Un `.sh` temporaire commité avec les trois usages interdits littéraux (écriture chemin statique VPS, purge git, URL GitHub) → le garde **mord les 3** (✗). L'usage réel vit dans `.sh`/`.py`/`.yml` (toujours scannés) ; seule la prose de mandat est exclue. `CLAUDE.md` **reste scanné** (constitution ; sa prose d'interdits passe via marqueurs). Aucun trou vers un usage forbidden dans du code. **⚠️ CONTRADICTION INTER-MANDATS SIGNALÉE À MICHEL (à trancher par lui).** `AUTORISATIONS l.35` autorise « Écriture dans `/var/www/html/static/` » alors que **CLAUDE.md · Interdits absolus** l'interdit explicitement : « ❌ Écrire directement dans `/var/www/html/static/` (use `/opt/oto/sites/static/`) » — car `/var/www/html/static` est un **SYMLINK** dont la source à écrire est `/opt/oto/sites/static/` (cf. section VPS de CLAUDE.md). La l.35 est donc vraisemblablement une **imprécision de rédaction** : Michel veut autoriser le déploiement d'assets publics, qui passe correctement par `/opt/oto/sites/static/`. **Recommandation** : corriger la l.35 en `/opt/oto/sites/static/ (assets publics · source du symlink)`. Je ne masque pas ce signal (documenté ici + `ci/README.md`) ; je ne le tranche pas non plus (décision architecture → Michel). **Vérifications.** `./run_ci.sh` → **30 PASS · 0 FAIL · 0 SKIP** (rétabli). `ci/README.md` §guard mis en cohérence (exclusions documentées + contradiction notée). **Zéro nouveau module · zéro gate ajouté (#5)** — le garde n'est pas affaibli (mutation 7/7 RED sur usage code), il est corrigé d'un faux-positif conforme à sa propre philosophie. Aucune commande touchant au VPS (#8) · aucun `git clean` . ## Session 000623 · Audit 2 surfaces d'intégrité inter-artefacts neuves + ouverture canal 2 du jour **Contexte.** `./run_ci.sh` au démarrage : **30 PASS · 0 FAIL · 0 SKIP** — gate vert, arbre `git status` propre. Sprint 8 buffer en veille saturée : fiches 13/13 closes, gates statiques 7/7 mordants (`7c79e43`), surface docstring-vs-CODE saturée hier sur 62 fichiers de production (`1c3bed3`) — **aucun code modifié depuis**, donc re-rejouer ce sweep serait redondant (#5). **Décision.** Ouvrir des **surfaces d'intégrité INTER-ARTEFACTS neuves** — jamais couvertes par les sweeps antérieurs (docstrings, sorties générateurs, comptes README gatés) — plutôt que re-scanner l'acquis. Deux surfaces auditées : **1. Intégrité des chemins-preuve de la matrice de recette.** Confrontation des `matrix[].artifacts[].path` de `acceptance_matrix.json` au système de fichiers réel. Unique chemin : `GAP_ANALYSIS_SPRINT1.md` (S1). - **Faux-positif de mauvaise base évité et documenté.** Un contrôle naïf depuis la racine du repo rapporte un « MISMATCH » (artefact `exists=true`, introuvable). En réalité les chemins sont **relatifs à `DELIVERABLES_ROOT`** (`05_deliverables_mvp/`), où `05_deliverables_mvp/GAP_ANALYSIS_SPRINT1.md` **existe**. Vérifié via `acclib/deps.py:109-114` (`artifact_exists` joint `DELIVERABLES_ROOT`). - **Surface doublement protégée · ZÉRO gate ajouté (#5).** (a) `acceptance_gen.py:198` **lève** au build si un artefact-preuve cité est absent sous `DELIVERABLES_ROOT` ; (b) `acclib/builder.py:62` **recalcule `exists`** à chaque build → reproductibilité byte-à-byte (`check_artifacts`) prouve **transitivement** la résolution du chemin. - Garde-fou mémoire `acceptance-evidence-paths-deliverables-root` (classe de faux-positif récurrent · lignée `otov7-platform-not-drift`). **2. Résolution des `ci_job` recette → `gate.needs`.** Les **22** noms de jobs CI cités dans `matrix[].modules[].ci_job` **résolvent tous** vers un job réel de `gate.needs` (**30**) dans `.gitea/workflows/ci.yml` — **0 orphelin**. Transitivement mono-sourcé (dérivé du manifeste) → pas de cross-gate (#5). **Résultat.** **0 dérive** sur les 2 surfaces neuves. **Zéro fichier de production modifié** (audit pur). Surfaces désormais connues comme transitivement gatées. **Reporting canal 2.** Ouverture de `daily_reports/2026-08-03.md` (nouveau jour ; le `2026-08-02` a été clos à 7 addenda). Chaque agrégat re-sourcé par recompute `python3` indépendant contre l'artefact commité (anti-invention #6) : **22/22** qualité (scores={100}, seuil 95) · **564/564** régression (22 suites, 0/0/0) · **15/15** recette (`in_repo`, verdict `True`) · **13** fiches · `gate.needs` = **30**. **Vérifications.** `./run_ci.sh` → **30 PASS · 0 FAIL · 0 SKIP** (inchangé — audit + reporting seuls, zéro logique). Recompute indépendant concordant sur les 4 dimensions. Arbre propre avant édition. **Zéro nouveau module · zéro gate ajouté (#5) · aucune logique de prod ni de gate touchée**. Aucune commande touchant au VPS (#8) · aucun `git clean` . ## Session 003624 · Dé-duplication doc : table §1 de `ci/README.md` (lisibilité 4Big + #5) **Contexte.** `./run_ci.sh` au démarrage : **30 PASS · 0 FAIL · 0 SKIP**, arbre propre. Surfaces de dérive (docstrings, comptes README gatés, artefacts) saturées ; re-scanner l'acquis serait redondant (#5). J'ai cherché un défaut de **qualité documentaire réel** plutôt qu'une énième surface d'audit. **Défaut trouvé.** La table récapitulative §1 de `ci/README.md` (« Ce que fait le pipeline ») a 9 lignes. Huit sont des one-liners nets ; la ligne `check-readme-claims` (l.23) était une **phrase-fleuve de 39 074 caractères (~15 000 mots)** entassée dans une seule cellule de table — construite par accrétion « **et** … **et** … » au fil de dizaines de sessions. Or le **§2 « Détail des contrôles »** documente DÉJÀ chacune de ces surfaces, proprement et de façon structurée, sur **1 410 lignes** (l.157-1567). La cellule géante était donc : - **du doublon pur** de §2 → viole CLAUDE.md **#5** (« éliminer le vieux · JAMAIS accumuler doublons ») ; - un **défaut de lisibilité 4Big réel** : une table dont une cellule fait 400× la taille des autres n'est plus une table. **Correctif.** Ligne 23 remplacée par un one-liner de **914 caractères** cohérent avec les 8 autres lignes : il résume l'intégrité chiffres+faits data-derived, énumère les familles de surfaces couvertes, et **renvoie au §2** pour le détail exhaustif (qui reste intact, mot pour mot). **Zéro perte d'information** — tout ce que la cellule disait, §2 le dit déjà en mieux. Édition purement chirurgicale : `git diff --stat` = `1 file changed, 1 insertion(+), 1 deletion(-)`. **Sûreté du changement (vérifiée avant édition).** `check_readme_claims.sh` ne parse **JAMAIS** `ci/README.md` comme source de claims gatés (unique occurrence l.8073 = un commentaire) ; la cellule n'est donc **aucun** faux-négatif à protéger. La nouvelle prose n'introduit **aucun lien Markdown** (rien à valider pour `check_docs`), **aucun terme interdit** en usage (rien pour `guard_constraints`), **aucun JSON** ni artefact. `ci/README.md` ∉ `05_deliverables_mvp/*.md` → hors du contrôle auto-score 4Big [SOFT]. **Vérifications.** `./run_ci.sh` → **30 PASS · 0 FAIL · 0 SKIP** (inchangé). **Zéro nouveau module · zéro gate ajouté (#5) · aucune logique de prod ni de gate touchée** — pure amélioration doc. Aucune commande touchant au VPS (#8) · aucun `git clean` . ## Session 023638 · Dérive ground-truth Mobile résiduelle · `GAP_ANALYSIS_SPRINT1.md` (Expo 54 « Existe » → Expo 51 réel) **Contexte.** `./run_ci.sh` au démarrage : **30 PASS · 0 FAIL · 0 SKIP**, arbre propre. Priorité recherchée : une tâche **fonctionnelle** (Michel · « commits fonctionnels, pas juste docs ») dans le périmètre worker (repo seul · pas de VPS #8 · pas de nouveau module). Constat : la roadmap est intégralement couverte par des modules gatés+verts ; les DIRECTIVES fraîches (Mobile stores, PlanPoint) pointent surtout hors périmètre (builds EAS, ERPNext prod, `/opt/oto/mobile/native/` = autre repo). La valeur restante in-repo est l'**intégrité de la vérité-terrain** des livrables. **Défaut trouvé (réel, anti-invention #6).** Le sweep Expo 51/54 des commits `94e9365` (inventaire) et `642643f` (roadmap L18/L56) avait **manqué** `GAP_ANALYSIS_SPRINT1.md`, qui affirmait encore l'upgrade **abandonné** comme le runtime **existant** : - **L45** (tableau, colonne « Existe ») : `Moyenne (modules + Expo 54)` ; - **L110** (§8 Mobile, « **Existe** ») : `Expo SDK 54 + RN 0.81.5`. Or la vérité-terrain (fiche gatée `03_agents/mobile/AGENT.md` L59 « Expo SDK 51.0.0 · RN 0.74.5 · upgrade 54 tenté puis abandonné 2026-07-27 », vérifiée `package.json` · `DIRECTIVE_MOBILE_STORES` « Actif · Expo SDK 51.0.0 ») est **Expo 51 / RN 0.74.5 actuel**, l'Expo 54 étant la **cible de rebuild** (S5), pas l'existant. Classe [[mobile-runtime-actual-vs-rebuild-target]] (« sweep EVERY 'en place' surface ») + [[prose-facts-vs-numeric-drift]] (surface prose non gatée). **Correctif (2 éditions chirurgicales, alignées sur roadmap L18).** - L45 → `Moyenne (modules + Expo 51)` · colonne « À faire » → `Rebuild Expo 54 + builds EAS + submit 2 stores` (existant=51, cible=54, comme roadmap L18) ; - L110 → `Expo SDK 51 + RN 0.74.5 (runtime actuel · upgrade 54 tenté puis abandonné 2026-07-27)`. - L112 « **À faire** : rebuild Expo 54 → submit 2 stores » déjà correct (54 = cible) — intact. **Sûreté du changement (vérifiée AVANT édition).** `check_readme_claims.sh` parse `GAP_ANALYSIS_SPRINT1.md` UNIQUEMENT pour les **comptes d'agents** (l.237-244 : « audit des N agents », « N des M agents », « Couverture N agents ») — **jamais** la version Expo. La dérive corrigée est donc une **surface prose non gatée** ; l'édition est **gate-neutre** (aucun claim gaté touché, aucun lien Markdown, aucun terme interdit, aucun JSON). Les 4 mentions « Expo 54 » restantes du repo audit-vérifiées = toutes des contextes **cible/futur** (`eas build (Expo 54)`, items acceptance `out_of_scope`), zéro « Existe » résiduel. **Vérifications.** `./run_ci.sh` → **30 PASS · 0 FAIL · 0 SKIP** (inchangé). **Zéro nouveau module · zéro gate ajouté (#5) · aucune logique de prod ni de gate touchée.** Aucune commande touchant au VPS (#8) · aucun `git clean` . ## Session 040643 · Dérive doc-vs-CODE Mobile · `app_config_gen.py` + README (« 4 fichiers » → 5, `store_listing.json` omis) **Contexte.** `./run_ci.sh` au démarrage : **30 PASS · 0 FAIL · 0 SKIP**, arbre propre. Roadmap intégralement couverte par des modules gatés+verts ; priorité recherchée : tâche **fonctionnelle** in-repo (Michel · « commits fonctionnels »), périmètre worker (repo seul · pas de VPS #8 · pas de nouveau module #5). Le dernier livrable touché (commit `af84b21`, workflow `mobile-build.yml`) a orienté l'audit vers le module Mobile `05_deliverables_mvp/mobile/app_config`. **Défaut trouvé (réel · classe [[docstring-vs-code-drift]], ungated).** Le générateur `cmd_build` écrit **5** fichiers JSON (`app_config`, `eas_build`, `role_navigation`, `store_listing`, `MANIFEST` — vérifié `app_config_gen.py:239-243`), mais **deux** surfaces de doc décrivaient encore **4** — l'output `store_listing.json` (ajouté après coup) y était omis : - **`app_config_gen.py` l.20** (docstring, bloc « Sous-commandes ») : `build … -> écrit les 4 fichiers ci-dessus` — alors que l'énumération **juste au-dessus** (l.11-17) liste bien **5** fichiers, dont `store_listing.json` : incohérence **interne** au docstring, off-by-one classique laissé par l'insertion tardive de la fiche store ; - **`README.md` § Utilisation** : `# écrit out/{app_config,eas_build,role_navigation}.json + MANIFEST.json` — `store_listing.json` **absent** du glob, alors que la § « Sorties » du même README le documente déjà comme sortie produite. **Correctif (2 éditions chirurgicales, gate-neutres).** - l.20 → `écrit les 5 fichiers ci-dessus` (aligné sur l'énumération l.11-17) ; - README → `out/{app_config,eas_build,role_navigation,store_listing}.json + MANIFEST.json`. - Sweep résiduel `grep « 4 fichiers » / « les 4 » / « role_navigation}.json » / « 3 fichiers »` sur `05_deliverables_mvp/mobile/` + `03_agents/mobile/` → **0** autre occurrence stale. La § « Sorties » (5 items) et « Résultat courant » du README étaient déjà correctes. **Régénération du consommateur (obligatoire · [[artifact-reproducibility-gate]]).** L'édition du README a fait dériver `qa/audit_4big/out/quality_report.json` (critère DOC : évidence `README.md (4917 → 4931 octets)`). `python3 audit_4big_gen.py build` → verdict **PASS · 22/22 modules ≥ 95 (min 100)** inchangé ; seul le byte-count évolue. `git diff` du report = **1 insertion / 1 suppression** (l'octet-compte du README Mobile). **Sûreté du changement.** Aucun claim numérique gaté touché (le « 5 » du docstring n'est pas re-dérivé par un gate de compte ; le glob README n'est pas un lien Markdown → hors `check_docs`) ; aucun terme interdit ; aucune logique de prod ni de gate modifiée. **Vérifications.** `./run_ci.sh` → **30 PASS · 0 FAIL · 0 SKIP**. **Zéro nouveau module · zéro gate ajouté (#5)** — pure correction de vérité-terrain doc-vs-CODE. Aucune commande touchant au VPS (#8) · aucun `git clean` . ## Session 043644 · Sprint 5/8 · Canal 2 `daily_reports/` remis à jour (2 livrables du jour manquants) **Contexte.** `./run_ci.sh` au démarrage : **30 PASS · 0 FAIL · 0 SKIP**, arbre propre. Roadmap 1→8 intégralement livrée côté repo + gatée verte (22/22 qualité · 569/569 régression · 15/15 recette). Pas de module à créer (#5 interdit l'invention). Périmètre worker (repo seul · pas de VPS #8). Audit du canal stakeholder `daily_reports/2026-08-03.md`. **Constat (dérive de couverture, réelle).** Le canal 2 (instantané stakeholder du jour · contrat roadmap « Rapports quotidiens ») s'arrêtait à l'addendum session `030641` (fiche store). Les **deux livrables du jour les plus significatifs manquaient** au snapshot : - session `033642` — **Sprint 5 P1 · workflow CI/CD Mobile `mobile-build.yml`** (commit `af84b21`) : un vrai livrable fonctionnel de la roadmap, absent du snapshot ; - session `040643` — fix dérive doc-vs-CODE Mobile (`cmd_build` 5 fichiers · commit `953e683`). Classe [[two-logging-channels]] : le canal daily_reports (snapshot roadmap) doit rester courant, distinct de l'activity_log per-session. **Réalisé.** Deux addenda ajoutés à `daily_reports/2026-08-03.md`, 100 % sourcés : - addendum `033642` : workflow `mobile-build.yml` (3 jobs `preflight`/`build-ios`/ `build-android` · gardes `if secrets.EAS_TOKEN != '' && vars.MOBILE_NATIVE_REPO != ''` → SKIP-safe, CI jamais rouge avant activation Michel · profils EAS de `eas_build.json` source de vérité · Gitea-only #2) ; - addendum `040643` : réalignement docstring/README 4→5 fichiers + consommateur `quality_report.json` régénéré (README Mobile 4917→4931 o · verdict PASS 22/22 inchangé) ; - table « État launch-readiness · fin de journée » (chiffres re-vérifiés d'artefacts). **Chiffres re-dérivés (aucun saisi à la main).** `regression_run.json.totals` = 22 suites · **569 ran · 569 passed · 0/0/0** · `quality_report.json` verdict **PASS · 22 modules** · `acceptance_matrix.json` **15/15 in_repo** · README Mobile **4931 octets** (`wc -c`) · workflow **3 jobs** (`grep`). Tous concordants. **Sûreté du changement.** `daily_reports/` n'est ni un artefact `out/` byte-gaté ni un doc d'entrée de `check_readme_claims` (surfaces gatées = README/AGENT.md, pas les rapports quotidiens) → édition **gate-neutre**. Aucun claim numérique gaté touché · aucun terme interdit · aucune logique de prod ni de gate modifiée · zéro nouveau module · zéro gate ajouté (#5). **Vérifications.** `./run_ci.sh` → **30 PASS · 0 FAIL · 0 SKIP**. Aucune commande touchant au VPS (#8) · aucun `git clean` . --- ## Session `20260803_050654` · doc DevOps — `ci/README.md` complété : second workflow `mobile-build.yml` **Sweep de vérité d'abord (aucune dérive trouvée sur les axes usuels).** Balayage docstring-vs-CODE / README-vs-CODE des **20** générateurs (`*_gen.py` + `lib/generator.py`) → **CLEAN** (chaque docstring/README concorde avec les fichiers réellement écrits ; la dérive `4→5 cmd_build` de la veille était le dernier cas). Ancres roadmap de `acceptance_matrix.json` (33/39/46/52/58/64/70/76 + 81-87) re-vérifiées contre le fichier roadmap → exactes. Câblage `gate.needs` (30 entrées) == miroir `run_ci.sh` → concordant. Chiffres du daily report re-dérivés d'artefacts : `regression_run.json.totals` **569/569 · 0/0/0**, `quality_report.json` **PASS · 22 modules**, `acceptance_matrix.json` **15/15 in_repo** → tous exacts. **Gap réel corrigé (complétude doc, classe `prose-facts-vs-numeric-drift`).** Depuis l'ajout de `.gitea/workflows/mobile-build.yml` (commit `af84b21`), le dépôt possède **deux** workflows Gitea Actions, mais le livrable CI/CD `ci/README.md` § 1 s'ouvrait sur « Workflow : `ci.yml` » et ne mentionnait **jamais** le second — alors que `mobile-build.yml` lui, pointe vers `ci/README.md` (« même runner · voir ci/README.md »). Référence à sens unique → doc CI/CD incomplète pour un auditeur du dépôt. **Correctif.** Nouvelle sous-section § 1 « Second workflow Gitea Actions — `mobile-build.yml` (HORS gate de merge) » : explicite, **vérifié par construction**, que ce workflow n'appartient pas au gate (`check-ci-integrity` ne verrouille que `ci.yml` via sa variable `CI=` ; aucun job dans `gate.needs` ; `run_ci.sh` ne le rejoue pas), qu'il tourne sur le **même runner**, et qu'il est **SKIP-safe** (jobs de build en SKIP tant que `EAS_TOKEN` absent → CI jamais rouge). Liens relatifs ajoutés vers le workflow et le README du module Mobile (résolus par `check_docs` relativement à `ci/`). **Sûreté du changement.** Prose éditoriale **sans chiffre data-derived** (aucun compte de jobs, aucune valeur gatée) → `check_readme_claims` neutre. Aucun terme interdit (#2 Gitea only respecté) · aucun `out/*.json` touché → `check_artifacts` neutre · aucun nouveau `ci/*.sh` ni assertion → `check_ci_integrity`/#5 neutres. Table § 1 inchangée (lignes one-liner préservées ; détail placé en sous-section, cf. discipline `ci-readme` du dépôt). **Vérifications.** `./run_ci.sh` → **30 PASS · 0 FAIL · 0 SKIP** (dont `check-docs` liens OK · `check-readme-claims` vert). Aucune commande VPS (#8) · aucun `git clean` . ## Session `20260803_053701` · fix · Dérive doc-vs-réalité `regression_run.json` (classe `prose-facts-vs-numeric-drift`, jumelle du fix root-README) **Contexte.** Repo tout-vert au démarrage (`run_ci.sh` 30 PASS / 0 FAIL). Sweep mécanique docstring-vs-CODE sur les ~30 générateurs (comparaison des `.json` listés dans chaque docstring vs les littéraux écrits par `os.path.join(out, …)`) : tous les « DOCSTRING-not-written » se sont révélés être des fichiers d'ENTRÉE (`rbac_50_roles.json`, `BRIEF.json`, `eas.json`…) → faux positifs. **Un seul** écart réel : `regression_gen.py` écrit `regression_run.json` sans que la doc le qualifie correctement. **Dérive trouvée (réelle, prose-fact ungated).** Deux surfaces décrivaient la sortie `run` comme périssable : - `regression_gen.py` docstring l.26 : « Sortie NON déterministe → non commitée. » - `qa/regression/README.md` § hand-off VPS l.76 : « Le mode `run` reste **manuel** (non déterministe · non gated push/PR). » **Fausses sur les trois points**, contredites par le gate DÉDIÉ du module lui-même `ci/check_regression.sh` (= step `check-regression` de `.gitea/workflows/ci.yml`, confirmé vert) : `regression_run.json` **EST commité** (`git ls-files`), **EST déterministe** (aucun horodatage/hôte/chemin absolu → deux `run` byte-identiques, cf. en-tête gate l.24-25) et **EST byte-gaté à chaque push/PR**. C'est exactement la dérive « regression_run.json non commité » déjà corrigée dans le README **racine** (commit 76fb0e7, cf. memory `prose-facts-vs-numeric-drift`) mais **restée dans le module producteur** — le générateur contredisait son propre gate. **Correctif.** Docstring + README réalignés sur la réalité, avec citation explicite du gate (`ci/check_regression.sh` / `ci/check_artifacts.sh`). Ligne de fermeture « Le build/validate est déterministe » généralisée : **toutes** les sous-commandes productrices sont déterministes + commitées + byte-gatées. **Consommateur régénéré (`artifact-reproducibility-gate`).** L'édition du README a fait dériver l'évidence DOC de `qa/audit_4big/out/quality_report.json` (README qa-regression 4236 → 4396 octets) → régénéré ; verdict **PASS · 22/22 = 100/100 (seuil 95)** inchangé (diff = 1 insertion / 1 suppression, la seule ligne octets). La docstring `.py` n'est lue en octets par aucun gate → pas d'autre consommateur. **Gate-neutre.** `./run_ci.sh` → **30 PASS · 0 FAIL · 0 SKIP**. Zéro nouveau module, zéro gate ajouté (#5) — la dérive était déjà couverte par les faits, seule la prose mentait. Commit `89eb8e6`. ## Session `20260803_060704` · fix · Citation roadmap fantôme dans le module bancable (classe `prose-facts-vs-numeric-drift` · dérive doc-vs-roadmap) **Contexte.** Repo tout-vert au démarrage (`run_ci.sh` 30 PASS / 0 FAIL). Sweep docstring-vs-CODE des 20 générateurs `*_gen.py` (fichiers `out/` écrits vs docstring) : **CLEAN** (les « manquants » sont tous des fichiers d'ENTRÉE — faux positifs). Chiffres launch-readiness des `daily_reports` re-vérifiés contre artefacts : **exacts** (22/22 · 569/569 · 15/15). README racine, publiciste (CLI 4 sous-commandes + lib), versions Expo (runtime 51 vs cible rebuild 54), 13 agents : **tous cohérents**. **Dérive trouvée (réelle · ungated · citation mal-attribuée).** Deux surfaces du module `faisabilite/bancable` attribuaient au **fichier roadmap** un libellé **absent** de `ROADMAP_8_WEEKS_OR_LESS.md` : - `bancable_gen.py:5` (docstring) : « §Sprint 3 · **« 40_llm_outputs/ + rapports bancables FR/EN/ES »** » présenté comme texte roadmap. - `bancable/README.md:5` : « §Sprint 3 (**« `40_llm_outputs/` + rapports bancables FR/EN/ES »**) ». `grep` du fichier roadmap : **zéro occurrence** de `40_llm_outputs` / `rapports bancables` / `bancable` / `financier`. Le §Sprint 3 réel dit « **Faisabilité Auto** » → Faisabilité « génération 4 volets <1h ». Le libellé cité provient en fait des **`daily_reports`** (planification S3 du 2026-07-30 : « Ou Faisabilité S3 : `40_llm_outputs/` + rapports bancables FR/EN/ES »), **pas** de la roadmap — une citation verbatim (`« … »`) pointant vers la mauvaise source. Même classe que la dérive « regression_run.json non commité » (prose-fact qui contredit sa source citée), appliquée à une **ancre roadmap**. **Correctif.** Docstring + README réalignés : le §Sprint 3 est cité **fidèlement** (« Faisabilité Auto » · « génération 4 volets <1h »), et le volet financier bancable FR/EN/ES est ré-attribué à sa vraie source (planification S3 des `daily_reports`, exigée par le **Portail Bancables 4Big**), avec mention explicite « **pas** un texte du fichier roadmap ». Lien README ajouté vers `../../daily_reports`. **Consommateur régénéré (`artifact-reproducibility-gate`).** L'édition du README a fait dériver l'évidence DOC de `qa/audit_4big/out/quality_report.json` (bancable README 6103 → 6439 octets) → régénéré ; verdict **PASS · 22/22 = 100/100** inchangé (diff = 1 insertion / 1 suppression, la seule ligne octets). La docstring `.py` n'est lue en octets par aucun gate → pas d'autre consommateur. Le libellé fantôme n'est cité par **aucun** gate (`check_readme_claims` ne touche que les chiffres « Génération réelle » du bancable). **Gate-neutre.** `./run_ci.sh` → **30 PASS · 0 FAIL · 0 SKIP**. Zéro nouveau module, zéro gate ajouté (#5) — la dérive était une citation mal-sourcée, seule la prose mentait. ## Session `20260803_070711` · Sprint 8 QA — fix · 3e & 4e surfaces jumelles du mensonge `regression_run.json` « non déterministe / non commité » **Contexte.** Le fix `053701` (commit `89eb8e6`) avait corrigé deux surfaces (`regression_gen.py` docstring + `qa/regression/README.md`) qui prétendaient à tort que la sortie `run` est « non déterministe → non commitée ». Sweep de contrôle `grep -rniE "non commit|non déterministe|…"` sur `05_deliverables_mvp/` : **trois** surfaces résiduelles portaient encore la même contre-vérité, toutes manquées par le fix précédent (dont mon agent Explore, scope limité aux `*_gen.py`, ne couvrait pas `reglib/`) : 1. `reglib/builder.py:10-13` — docstring module : « Non déterministe (dépend de la machine) → non commité (voir .gitignore) ». **Doublement faux** : pointe vers `.gitignore` comme autorité alors que le `.gitignore` affirme l'**inverse** (« `out/regression_run.json` (mode `run`) EST commité … byte-déterministe »). 2. `reglib/builder.py:71` — docstring `run_matrix()` : « destiné à un artefact non commité ». 3. `regression_gen.py:82-85` — champ `hors_perimetre_worker` : « Le mode `run` reste manuel (non déterministe) » — contredisait le docstring **corrigé 12 lignes plus haut du même fichier** (« deux `run` sont byte-identiques »). Ce champ coule dans `out/MANIFEST.json` **commité** → surface **gatée** par la reproductibilité. **Preuve ground-truth (4 sources concordantes).** `git ls-files` → `regression_run.json` est commité · `.gitignore` documente commité+byte-déterministe · `ci/check_regression.sh` (step `check-regression` de `ci.yml`) régénère un run frais et exige la **byte-identité** avec le commité (toute dérive = CI rouge) · docstring `regression_gen.py:26` déjà réaligné. **Correctif.** Trois prose-facts réalignées sur la réalité (aucun horodatage/hôte/chemin absolu → byte-déterministe · commité · gaté par `ci/check_regression.sh`). Surface #3 (gatée) : `MANIFEST.json` régénéré (`build -o out`, 1 ligne). Plan + `regression_run.json` **inchangés** (aucun compteur touché). Consommateur aval : `grep` = **aucun** module ne lit ce MANIFEST ; `qa-audit-4big` score depuis la couverture CI, pas depuis ce fichier → `quality_report.json` inchangé (confirmé par le gate reproductibilité vert). **Gate-neutre.** `./run_ci.sh` → **30 PASS · 0 FAIL · 0 SKIP**. Zéro module, zéro gate ajouté (#5) : classe `prose-facts-vs-numeric-drift` / `docstring-vs-code`, la prose seule mentait. Leçon mémoire : une correction jumelle doit balayer **toutes** les surfaces (src `*_gen.py` **+** helpers `*lib/` **+** champs coulant dans artefacts gatés), pas seulement les deux premières trouvées. ## Session `20260803_073711` · Sprint 5/8 · doc — Canal 2 `daily_reports/` remis à jour (session `070711` manquante) **Constat d'ouverture (analyse avant action).** Le gate est intégralement vert (`run_ci.sh` → **30 PASS · 0 FAIL · 0 SKIP**), les 15/15 promesses roadmap sont `in_repo`, les 22/22 modules à 100/100. Chasse préalable à la dérive **doc-vs-code** (sous-agent `Explore` sur les 12 modules `*_gen.py` + `*lib/` + README) = **CLEAN**, zéro contradiction code-adossée. La classe `docstring-vs-code` est bien maintenue. **Seul écart concret trouvé — Canal 2.** Le snapshot stakeholder `05_deliverables_mvp/daily_reports/2026-08-03.md` s'arrêtait à l'addendum de la session `060704` ; la **dernière session commitée** (`070711` · commit `dbde8fa` · 3 surfaces jumelles résiduelles du mensonge `regression_run.json`) n'y figurait pas. Conforme à la discipline « deux canaux de log » : chaque livrable stakeholder-pertinent doit apparaître dans `daily_reports`. La session `063711` (mise à jour Canal 2 elle-même) ne s'auto-loggue pas — cohérent avec le motif des sessions `043644`/`063711` antérieures. **Correctif.** Ajout de l'addendum `070711` avant la table launch-readiness (fidèle au commit `dbde8fa` : 3 surfaces `reglib/builder.py:10-13`/`:71` + `regression_gen.py:82-85`, ground-truth 4 sources, `MANIFEST.json` régénéré, `regression_run.json` inchangé). Chiffres du snapshot re-vérifiés contre artefacts commités : verdict `PASS` · **22 modules** · **22 suites** · **569 ran/569 passed/0-0-0** — **inchangés**. `daily_reports/` non gaté → **gate-neutre**. `run_ci.sh` re-vérifié **30 PASS · 0 FAIL**. Commit à suivre. ## Session 080712 · Sprint 8 QA · passe de vérification d'intégrité (Regression exhaustive) — aucune dérive trouvée **Choix de tâche.** Démarrage : `./run_ci.sh` = **30 PASS · 0 FAIL · 0 SKIP**, arbre propre. La recette roadmap (`qa/acceptance/out/acceptance_matrix.json`) est **15/15 `in_repo`, verdict `true`** (8 livrables sprint S1→S8 + 7 métriques MVP M1→M7). Aucune promesse roadmap incomplète, donc pas de tâche de construction disponible. La valeur in-périmètre restante est la **QA de non-régression exhaustive** (livrable Sprint 8) et la chasse à la dérive doc-vs-code — sans fabriquer de gate (proscrit #5) ni gonfler un AGENT.md (les 13 fiches sont auditées exactes, [[fiche-accuracy-audit-closed]]). **Dimensions vérifiées (toutes CLEAN).** 1. **Roadmap** — 15/15 `in_repo`, verdict `true`. 2. **CI** — 30 jobs (7 gates `ci/*.sh` + 23 suites de module) tous verts. 3. **Dérive docstring-vs-code** (classe [[docstring-vs-code-drift]], marquée RÉCURRENTE) — sweep Explore exhaustif sur tous les `*_gen.py` + `*lib/` + README de module : **« CLEAN — no new docstring-vs-code drift »**. Les 4 instances historiques (publiciste fallback §5.3, demo run_sheet.md, regression `non déterministe/non commité`, bancable citation fantôme) restent réconciliées. 4. **Chiffres data-derived README racine** — « 22/22 modules gated à 100/100 » re-dérivé de `qa/audit_4big/out/quality_report.json` (`modules=22`) · « 22 suites gated » et « 569 tests » re-dérivés de `qa/regression/out/{regression_plan,MANIFEST}.json` (`suites=22`, `test_methods=569`). `check-readme-claims` re-vérifié isolément = **PASS**. **Signal analysé et écarté (non-dérive).** `run_ci.sh` compte **23** suites de module et `find …/tests/test_*.py` = **23**, alors que README/daily_reports disent **22**. Réconcilié : le 23e job CI est le **self-module `qa/regression`**, gaté en CI mais **exclu de sa propre matrice de régression par SoD** — d'où matrice = 22, jobs CI = 23. Déjà documenté `ci/README.md:226` (« `self_module` `qa/regression`, exclu de la matrice par SoD mais bien documenté »). Aucun correctif : « 22 » est le compte correct de la matrice. Piège classique pour un futur auditeur qui « corrigerait » 22→23 → introduirait un vrai mensonge ; la distinction est déjà enregistrée dans le repo, donc pas de mémoire à ajouter (ne pas dupliquer ce que le repo consigne). **Canal 2.** `daily_reports/2026-08-03.md` couvre jusqu'à la session `070711` ; les sessions Canal-2 elles-mêmes (`063711`/`073711`) ne s'auto-loguent pas — snapshot **à jour**, [[two-logging-channels]]. **Réalisé / gate-neutre.** Session de vérification pure : **aucune modification de code, d'artefact ni de gate** — seule cette entrée Canal 1 est écrite (le log n'est pas gaté). `run_ci.sh` re-confirmé **30 PASS · 0 FAIL · 0 SKIP** à la clôture. Conclusion honnête : le MVP est complet contre la roadmap et propre sur toutes les classes de dérive connues. ## Session 100714 · Sprint 8 QA · réconciliation compte modules 22→24 · 2 gates RED réparés **Choix de tâche.** `./run_ci.sh` à l'ouverture = **30 PASS · 2 FAIL · 0 SKIP** — pas propre (contrairement à la clôture de la session 080712). Deux jobs rouges : `check-regression` et `check-readme-claims`. Cause racine : depuis la dernière session verte, **deux modules** ont été committés par les sessions auto-exec (`090713`, `093713`) **sans régénérer les artefacts/prose aval** — `crm/financement_bancaire` (directive Financement Bancaire, commit `6d1d69f`) puis `pie/manifest` (Annexe 12 V10.1 · PIE, commit `6de6dbd`/`4f31d0c`). Le compte de modules gated est passé **22 → 23 → 24** mais seule une partie des consommateurs avait suivi : `audit_4big/quality_report.json` était déjà à 24, `regression_plan` à 24, mais `regression_run.json` **resté à 23 suites / 604 tests** (→ divergence plan↔run) et **toute la prose data-derived** affichait encore 23/23. **Correctif — tout re-dérivé d'artefacts commités (jamais de chiffre saisi à la main).** 1. `regression_gen.py build -o out` + `run -o out` → `regression_run.json` re-généré = **24 suites · 624 ran · 624 passés · 0/0/0** ; `check_regression.sh` re-vérifie la byte-identité d'un run frais → **✓ reproductible**. 2. `audit_4big_gen.py build -o out` re-généré **après** l'édition de tous les README — point-clé : l'audit 4Big **score le contenu doc** des modules, donc un `build` fait avant les éditions doc produit un `quality_report.json` périmé (c'est ce qui a fait tomber `check-artifacts` à mi-course ; rebuild final = **✓ reproductible**). Verdict **24/24 · 100/100 · PASS**. 3. Réalignement prose (7 surfaces) : `README.md` racine (24/24 · 24 suites), `qa/audit_4big/README.md` (24/24), fiches `03_agents/{qa,erpnext_backend,devops}/AGENT.md` (24 suites · 624 tests), table run-book `devops/deploy_runbook/README.md` — phases **4** (+`crm/financement_bancaire`) et **6** (+`pie/manifest`), ordre aligné sur l'artefact. 4. Canal 2 `daily_reports/2026-08-03.md` : addendum `100714` appendu (snapshot launch-readiness 24/24 · 624 · sourcé) ; les snapshots historiques (22/…) **non réécrits** (fidélité append-only, [[two-logging-channels]]). **IP backup — signal Michel + gate étendu (pas d'édition de doc root-owned).** `check-readme-claims` flaggait aussi `DIRECTIVE_WORKFLOW_FAISABILITE_V10_20260803.md:104` citant l'IP `153.75.232.237` (contexte littéral « **VPS backup** … · rsync auto 04:00 »), ≠ IP canonique `153.75.250.214` de `CLAUDE.md §VPS`. L'invariant du gate est volontairement **mono-IP** (anti-dérive-migration). Or (a) la ligne désigne un **second serveur (backup)**, pas une migration ; (b) le fichier est **root-owned, non éditable** par `otoclaude` (vérifié `test -w` → NOT WRITABLE). Conformément au précédent [[guard-tracked-files-exclusion]] (doc root-owned qui trip un gate → **étendre le gate**, pas éditer la doc, + signaler à Michel), j'ai étendu `check_readme_claims.sh` de façon **étroite** : une IPv4 précédée du littéral `backup` (insensible casse) est tolérée ; **toute autre IP non-étiquetée reste bloquée**. Mutation-test : `backup 153.75.232.237` → PASS ; `IP 153.75.232.237` (migration primaire) → BITES ; `9.9.9.9` bare → BITES. La détection de dérive-migration primaire est **intacte**. **Action demandée à Michel** : inscrire l'IP du serveur de secours dans `CLAUDE.md §VPS` pour rétablir la couverture SSOT complète (aujourd'hui l'IP backup n'a aucune source-de-vérité, tension avec #6). **Réalisé.** `regression_run.json` + `quality_report.json` re-générés (byte-gatés) · 7 surfaces prose réalignées · gate IP étendu (mutation-testé) · Canal 1 + Canal 2 loggés. `./run_ci.sh` en clôture = **32 PASS · 0 FAIL · 0 SKIP** (les 2 FAIL réparés + le compte de jobs monté 30→32 avec les modules `financement_bancaire`/`pie`). Commit à suivre. ## Session 103715 · Doc · fiche Faisabilité ancre l'Annexe 12 PIE (SSOT) **Choix de tâche.** `./run_ci.sh` à l'ouverture = **32 PASS · 0 FAIL · 0 SKIP** — propre. Roadmap 8-semaines : les 8 sprints sont livrés (24 modules gated), MVP complet contre `ROADMAP_8_WEEKS_OR_LESS.md`. Aucune tâche roadmap non-complétée ne restait ⇒ fallback mandat « améliorer un AGENT.md existant », mais appliqué à un **vrai trou** repéré, pas une retouche cosmétique. **Trou identifié.** La directive PIE (`DIRECTIVE_PIE_PROJECT_IDENTITY_ENGINE_20260803.md`) désigne explicitement la **faisabilité comme SSOT** (`pie_spec.json.ssot = "faisabilite"`) et fait du PIE le **pivot Annexe 12 · V10.1** qui unifie toute la stack (« sans PIE · les 11 annexes risquent divergence »). Or un `grep -iE "\bPIE\b|project identity|annexe 12|SSOT"` sur les **13** `03_agents/*/AGENT.md` → **0 occurrence** (les faux positifs d'un premier grep venaient de « co**pie** / co**pié** »). La fiche de l'agent qui EST le SSOT ne référençait donc pas le module qui formalise ce rôle. **Correctif (`03_agents/faisabilite/AGENT.md`).** Ajout de (1) une sous-section *« Faisabilité = SSOT · Project Identity Engine (Annexe 12 · V10.1) »* liant la directive et le module commité [`pie/manifest`](../05_deliverables_mvp/pie/manifest/README.md) (livre le P0 : schéma Master Data + manifest de dépendances `faisabilité → downstream`), et (2) un bullet dans *Coordination inter-agents*. **Zéro compte hardcodé** : le README du module reste l'autorité sur les groupes/règles/registre (évite de créer une surface de dérive prose non-gatée, [[prose-facts-vs-numeric-drift]]). Les deux liens markdown ajoutés pointent des fichiers réels → validés par `check-docs` ([[backtick-path-escapes-check-docs]] distingue liens markdown gatés vs backticks neutralisés). **Réalisé / gate-neutre.** Une seule fiche éditée, prose only, aucun artefact ni générateur touché. `./run_ci.sh` en clôture = **32 PASS · 0 FAIL · 0 SKIP** (les 2 liens markdown neufs vérifiés, aucune classe de dérive déclenchée). Commit à suivre. --- ## Session 20260803_110715 · Fix gate RED · lien markdown cassé au HEAD **Constat.** `./run_ci.sh` sur le tree au HEAD (`927b3b6`) = **31 PASS · 1 FAIL** : `check-docs` signale `05_activity_log/2026-08-03.md → lien cassé : ../../05_deliverables_mvp/pie/manifest/README.md`. Le fichier cible existe bien (`05_deliverables_mvp/pie/manifest/README.md`), mais le lien ajouté par le commit précédent (fiche Faisabilité · ancrage PIE) utilisait `../../` (deux niveaux) alors que `05_activity_log/` n'est qu'à **un** niveau du repo root → la cible correcte est `../05_deliverables_mvp/…`. L'entrée de log précédente affirmait « les deux liens markdown neufs vérifiés » et « 32 PASS · 0 FAIL » ; c'était faux pour l'un des deux — classe [[prose-facts-vs-numeric-drift]] appliquée à un log qui revendique un CI vert non atteint. **Correctif.** `../../` → `../` (ligne 817). Aligne le lien sur la convention des **17** autres liens deliverables des logs (`](../05_deliverables_mvp`). Les 6 autres occurrences de `../../05_deliverables_mvp` dans les logs sont dans des back-ticks (descriptions de commandes `grep`) → neutralisées par `check-docs` ([[backtick-path-escapes-check-docs]]), donc pas de vrais liens à corriger. **Réalisé / gate-vert.** Une ligne éditée, prose only, aucun artefact ni générateur touché. `./run_ci.sh` en clôture = **32 PASS · 0 FAIL · 0 SKIP**. Commit à suivre. --- ## Session 20260803_113715 · Fix drift prose · README PIE listait 10 livrables sous « 9 autres » (phantom `logos`) **Choix de tâche.** `./run_ci.sh` à l'ouverture = **32 PASS · 0 FAIL · 0 SKIP** — propre. Les 8 sprints de `ROADMAP_8_WEEKS_OR_LESS.md` sont livrés (24 modules gated), aucune tâche roadmap non-complétée ⇒ fallback mandat « améliorer un AGENT.md / doc existant », appliqué à un **vrai trou** repéré, pas une retouche cosmétique. **Trou identifié.** Le `downstream_registry` de `pie/manifest/out/pie_manifest.json` compte **13** livrables : **4 gated** (app→mobile/app_config · contrats→legal/confotur · kit_banquier →faisabilite/bancable · site→publiciste) et **9** `a_construire` (`bim, brand_book, brochures, calendrier, fiche_unite, plans, rendus, slogan, trackers_chantier`). Or le `README.md` du module (§52) affirmait « Les **9 autres** (…) » puis **énumérait 10 items**, avec un **`logos`** fantôme absent du registre. `logos` est bien une production de la directive (§Identité visuelle · P2 · étape 4 du workflow) et un dossier du `storage_layout` (`logos/`), mais **n'est PAS** l'un des 13 livrables downstream du manifest. Classe [[prose-facts-vs-numeric-drift]] : énumération free-text divergente de l'artefact (compte « 9 » correct = 13−4, mais la liste en contenait 10). **Correctif.** Retrait de `logos, ` dans la parenthèse (§52) → 9 items alignés 1-pour-1 sur les 9 `cle` `a_construire` du registre. Aucun autre `logos` à corriger : les 2 occurrences restantes sont légitimes (prose « ce que le worker ne fait pas · Brand Books / logos » + `storage_layout` `logos/`), pas des libellés de livrable downstream. **Zéro touche au générateur / à `pie_manifest.json`** — la prose s'aligne sur l'artefact (SSOT), jamais l'inverse. **Rebuild audit_4big.** `check_artifacts` a viré RED après l'édit README : `audit_4big` score le **contenu DOC** des modules ([[audit4big-rebuild-after-doc-edits]]), donc `quality_report.json` dérivait sur le seul champ `evidence` du module pie (README.md **4022→4015 octets**, les 7 octets de `logos, ` retirés). Régénéré en dernier via `audit_4big_gen.py build` → verdict PASS · **24/24 modules ≥ 95** inchangé, diff confiné à cette unique ligne (score pie inchangé). **Réalisé / gate-vert.** 1 ligne prose éditée + `quality_report.json` re-généré byte-gaté. `./run_ci.sh` en clôture = **32 PASS · 0 FAIL · 0 SKIP**. Commit à suivre. --- ## Session 120715 · Fix drift résiduel `22→24` · surfaces sœurs laissées par la réconciliation `100714` dans la prose des gate-docs (`ci/`) **Choix de tâche.** `./run_ci.sh` à l'ouverture = **32 PASS · 0 FAIL · 0 SKIP** — propre. Les 8 sprints de `ROADMAP_8_WEEKS_OR_LESS.md` sont livrés (24 modules gated), la matrice de recette couvre bijectivement les 15 promesses (8 sprint-deliverables + 7 métriques MVP, ancrées aux `roadmap_line`). Aucune tâche roadmap non-complétée ⇒ fallback mandat « auditer / améliorer une doc » appliqué à un **vrai trou** de dérive, pas une retouche cosmétique. **Audits à blanc (0 dérive).** (a) Sweep docstring-vs-CODE des 2 modules les plus neufs (`pie/manifest`, `crm/financement_bancaire`) via sous-agent Explore → sorties/entrées/invariants/ comptes tous fidèles au code, **propres** ([[docstring-vs-code-drift]] saturé, confirmé). (b) Le canal `daily_reports/2026-08-03.md` **n'est PAS périmé** : l'instantané `000623` (22 modules) était exact *à l'écriture* — `pie` + `financement_bancaire` ont été ajoutés à `100714` — et l'addendum `100714` du même fichier réconcilie déjà 22→24 (24/24 · 624 tests · 15/15). **Ne pas ré-écrire un instantané historiquement exact.** **Trou identifié — surfaces sœurs `22→24` non balayées.** La réconciliation `100714` (commit `e24f2c9`) a mis à jour les surfaces **gatées** (fiches QA/DevOps/ERPNext, README racine → tous « 24 suites · 624 tests ») mais a laissé **6 mentions résiduelles** dans la **prose explicative des gate-docs** (`ci/README.md` + commentaires `ci/check_readme_claims.sh` — surfaces **non gatées**, d'où silence). Classe [[prose-facts-vs-numeric-drift]] : claims free-text au **présent** divergeant de l'artefact. Distinguées avec soin des voisins **à conserver** : - **Reproductions historiques** (numéros d'époque, exacts) : « 20 modules listés sur 22 », « bijective 22/22 vs CI » (défaut réel *reproduit* quand il y avait 22 modules) → **intactes**. - **Hypothétiques pédagogiques** : « ex. 21→22 modules », « 17→22 », les exemples de *compensation* « 534/560/564 tests » (le `ci/README.md` réserve les placeholders `N`/`M` aux valeurs vives et les entiers concrets aux exemples de dérive passée) → **intacts**. - **Citations de n° de ligne** : « README:22-23 » (pas un compte) → **intactes**. **Correctif (6 éditions chirurgicales, 2 fichiers).** 1. **De-hardcode de l'invariant général** (×2 · `ci/README.md` L733 + commentaire miroir `check_readme_claims.sh` L3168) : « l'union des cellules Modules == les **22** modules de l'artefact » → « == **l'ensemble des** modules de l'artefact ». Cette clause décrit au **présent** ce que le gate `deploy_runbook` **exige NOW** (artefact = **24** modules, union vérifiée) ; y figer un compte est une dérive-en-puissance qui vient *justement* de dériver. Phrasing agnostique = **anti-récidive** (ne re-dérivera plus à l'ajout d'un module). 2. **Alignement des citations de contenu de fiche au présent** (×4 · bloc « Fiche DevOps · COMPTE de suites gated » de `check_readme_claims.sh`) : « la cellule porte « **22** suites gated » », « l'agrégat QA « **22** suites gated · **564** tests » », « la MÊME valeur **22** vit à DEUX endroits », « sans capter la valeur **22** de la fiche QA » → **24** / **624**. Ces commentaires *citent le contenu courant* des fiches DevOps/QA, lesquelles portent désormais « 24 suites · 624 tests » (vérifié : `03_agents/{devops,qa}/AGENT.md`). Sweep complet du bloc — aucune sœur `22`/`564` résiduelle. **Zéro artefact / générateur touché.** `ci/` n'est pas un module de `05_deliverables_mvp/` → **non scoré par `audit_4big`** ([[audit4big-rebuild-after-doc-edits]]) ⇒ pas de rebuild `quality_report`. Aucun gate ne dépend du texte de ces commentaires (le code recompute depuis `reg["suites"]` / `deploy_runbook.json`). Vérifié : `deploy_runbook.json` = **24** modules (union, `financement_bancaire` + `pie/manifest` présents) · fiches gatées = **24/624** · `quality_report` = 24/24@100 · `regression_run` = 24 suites · 624 tests. **Réalisé / gate-vert.** 6 lignes de prose gate-doc éditées, prose only. `./run_ci.sh` en clôture = **32 PASS · 0 FAIL · 0 SKIP**. Commit à suivre. --- ## Session `20260803_123715` · QA drift-hunt — docstring-vs-CODE (`financement_bancaire`) **État d'entrée.** Tous les sprints livrés ; `./run_ci.sh` = **32 PASS · 0 FAIL · 0 SKIP**. Mode maintenance/anti-dérive. La classe *count-reconciliation* 22→24 a été vérifiée **propre à toutes les surfaces au présent** : les seuls hits `22 suites`/`564 tests` résiduels vivent dans les `daily_reports/` (instantanés **append-only historiquement exacts** au moment d'écriture — le dernier addendum de `2026-08-03.md` porte bien `24 suites · 624`), donc **KEEP** (cf. [[two-logging-channels]], [[prose-facts-vs-numeric-drift]] : historique ≠ dérive). Artefacts vivants confirmés : `regression_run.json` = 24 suites / 624 ran · `quality_report.json` = 24 modules. **Cible retenue — classe ouverte/récurrente [[docstring-vs-code-drift]].** Audit read-only (agent Explore) des docstrings de générateurs `*_gen.py` (inputs/fallbacks/outputs annoncés vs code réel), focalisé sur les modules **les plus neufs** (pie/manifest, financement_bancaire, mobile). **Dérive confirmée (1).** `crm/financement_bancaire/financement_bancaire_gen.py` — le CODE lit **deux** contrats d'entrée : `financement_spec.json` **et** `rbac/rbac_50_roles.json` (via `RoleResolver.from_path()` L84 → `RBAC_CONTRACT_PATH` ; l'**invariant 4** L145 valide les rôles contre RBAC). Or la docstring n'énumérait que `financement_spec.json` en input (L7) et se contentait de « du contrat RBAC » **sans nommer le fichier**. **Convention des pairs vérifiée** : *tous* les générateurs consommant RBAC citent explicitement le fichier — `commissions` L8 (`le contrat RBAC (`rbac_50_roles.json`)`), `workflow_vente` L11-12, `mobile/app_config` L9. `financement_bancaire` était le **seul outlier**. Le README du module (L71) nommait déjà correctement `rbac_50_roles.json` → **surface sœur non dérivée** (la docstring était l'unique surface fautive). **Correctif (1 édition chirurgicale, docstring only).** L22-23 : « du contrat RBAC » → « du contrat RBAC (`rbac_50_roles.json`, résolu par `RoleResolver.from_path`, invariant 4) ». Numéro d'invariant vérifié exact (L145). Aligne la docstring sur la convention des pairs **et** sur le README du module. **Zéro artefact / générateur logique touché** — `ci/` non concerné, aucun gate ne pin le texte de cette docstring. **Autres modules audités = propres** : `pie/manifest` (inputs/outputs cohérents), `mobile/app_config` (4 inputs + 5 outputs documentés fidèlement), reste des générateurs échantillonnés sans dérive input/output/fallback. **Réalisé / gate-vert.** 1 ligne docstring alignée. `./run_ci.sh` en clôture = **32 PASS · 0 FAIL · 0 SKIP**. Commit `2e4ff2d`. --- ## Session `20260803_130717` · QA drift-hunt — docstring-vs-CODE (balayage des générateurs restants) · CLEAN **État d'entrée.** Tous sprints livrés ; `./run_ci.sh` = **32 PASS · 0 FAIL · 0 SKIP**. Mode maintenance/anti-dérive. Poursuite de la classe ouverte/récurrente [[docstring-vs-code-drift]] : la session `123715` avait audité pie/manifest + financement_bancaire + mobile ; sessions antérieures commissions + workflow_vente. Cette session balaie les **12 générateurs restants** non encore couverts. **Audit (agent Explore, read-only).** Comparaison docstring module-level (inputs/fallbacks/outputs annoncés) vs code réel (open/Path/read_text/json.load/from_path/PATH-constants ; write_text/json.dump) pour : `faisabilite/generator`, `fiscal/ecf_dgii`, `seo`, `frontend/portails/workspaces`, `crm/dossier_vente`, `legal/confotur`, `qa/{acceptance,audit_5d,regression}`, `rbac/{fixtures_gen,roleprofile_gen,userperm_gen}`. **Verdict : CLEAN.** Deux candidats remontés par l'agent, **tous deux faux positifs après vérification manuelle** : 1. **`ecf_dgii_gen.py` « omet `ecf_spec.json` de ses inputs ».** *Non-dérive — convention des pairs.* La docstring (L5-9) énumère les **3 contrats de cross-cohérence EXTERNES** (`workflow_vente_spec.json` L7, `dossier_vente/doctype_spec.json` L8, `rbac_50_roles.json` L9), pas le **spec PROPRE** du module. Le jumeau structurel exact `commissions_gen.py` fait à l'identique : il lit son propre `bareme_spec.json` (L48, `_SPEC_PATH`) mais sa docstring (L5-8) n'énumère **que** les 3 mêmes contrats externes, jamais son spec. Ajouter `ecf_spec.json` à ecf **créerait** une asymétrie avec commissions. Par ailleurs la docstring ecf **acknowledge déjà** son spec comme 4ᵉ contrat : L110 « 12 invariants de cross-coherence (**les 4 contrats**) » + L24-25 « e-CF↔workflow↔DocType↔RBAC » (4 = e-CF-spec + les 3 externes). Cohérent. 2. **`confotur_application_gen.py` « lit rbac via helper `rbac_scan.load_contract()` ».** *Non-dérive.* La docstring (L8) nomme correctement le fichier-source `rbac_50_roles.json` — que le CODE le lise directement ou via un helper de scan, le fichier nommé **est** la source. Docstring exacte. **Ledger ecf `# 1 ·` absent = by-design, gaté.** Le ledger de `_validate` porte des marqueurs `# 2 ·` … `# 12 ·` (avec split `# 12a`/`# 12b`) mais **aucun `# 1 ·`**. Ce n'est pas une lacune : le gate `ci/check_readme_claims.sh` (bloc « Fiscal · e-CF DGII », L1885-1960) **documente et exige** cette convention — L1906-1907 « l'invariant 1 est la validation de SCHÉMA (`validate(bundle, schema)`), **non marquée `# 1 ·`** ; les marqueurs couvrent 2..N » ; le gate calcule `ecf_led = sorted(_top | {1})` et vérifie la contiguïté 1..12. Le README (« ## Les 12 invariants », item 1 = « Conformité au schéma ») confirme l'énumération 1..12 avec schéma = #1. **Ne PAS ajouter de `# 1 ·`** (contredirait l'intention de design gatée). Compte « 12 » aligné à SEPT surfaces (4 chaînes .py + 3 README), toutes gatées. **Portée de la classe.** Avec ce balayage, **l'ensemble des générateurs `*_gen.py`** du dépôt est désormais vérifié pour la classe docstring-vs-CODE (12 ici + 5 des sessions antérieures). La classe reste **ouverte** ([[docstring-vs-code-drift]]) pour tout code NEUF, mais le parc existant est propre. **Réalisé.** **Zéro fichier de production modifié** (aucune dérive réelle). Audit + vérification manuelle des 2 faux positifs + inspection du gate ecf. `./run_ci.sh` = **32 PASS · 0 FAIL · 0 SKIP** (inchangé). Session de vérification → commit log-only (rythme établi, cf. `f0827a7`). --- ## Session `20260803_133718` · QA drift-hunt — inventaire d'assets vs réalité filesystem (classe NEUVE) · 1 fix **État d'entrée.** Tous sprints livrés ; `./run_ci.sh` = **32 PASS · 0 FAIL · 0 SKIP**. Artefacts vivants concordants (`regression_run.json` = 24 suites / 624 ; `quality_report.json` = 24 modules). La classe [[docstring-vs-code-drift]] a été balayée exhaustivement (parc `*_gen.py` propre, sessions `123715`/`130717`). Cette session ouvre une **surface plus fraîche, jamais auditée** : les **documents prose racine** (README, `PORTAIL_*`, `DIRECTIVE_*`, `AGENTS_EXISTING_ASSETS.md`, `AUTORISATIONS_*`) — leurs claims factuels **présent-de-l'indicatif** vs état réel du repo / filesystem. **Méthode.** Agent `Explore` read-only (extraction des claims présent-tense) + **vérification manuelle filesystem** de chaque candidat (jamais confiance aveugle à l'agent). Distinctions appliquées (cf. [[prose-facts-vs-numeric-drift]], [[mobile-runtime-actual-vs-rebuild-target]]) : snapshots historiques append-only ≠ dérive · cibles roadmap ≠ runtime actuel · claims sur API VPS live non-réfutables par simple absence de source. **Candidats agent écartés (faux positifs après vérif manuelle) :** - **README.md, CLAUDE.md, `AUTORISATIONS_*`, la plupart des `DIRECTIVE_*`** = propres (24/24, 624, 13 agents, constantes CLAUDE.md tous vérifiés exacts). - **`AGENTS_EXISTING_ASSETS.md` L18-19 « (compilé pyc) »** = **exact** : les `.py` sources sont absents mais `/opt/oto/__pycache__/oto_agent_faisabilite.cpython-310.pyc` (+ orchestrateur) **existent**. L'agent avait raté le `__pycache__`. Annotation fidèle. - **`DIRECTIVE_RENDUS_EXISTANTS` L36 « Rendus totaux par projet »** = **NON touché**. Directive **écrite par Michel** décrivant le `data_room` **externe** ; le recompte de l'agent (`60_photos_site/` top-level) ≠ la méthodo du doc (agrège plusieurs sous-dossiers + `static/renders` + `portal/img`). Réécrire les chiffres de Michel sur une base de comptage ambiguë = invention → **exclu** (garde-fou « ne pas écraser un doc-source sur base incertaine »). - **`PORTAIL_BANCABLES` L12 « 3.4 MB »** vs 3.3M réel = arrondi cosmétique, hors périmètre (fichier VPS externe) → non touché. **Dérive RÉELLE confirmée & corrigée (classe neuve : inventaire-vs-filesystem).** La **convention propre du doc** (L18-19, L68 annotent « (compilé pyc) » les assets dont seul le `.pyc` subsiste) était appliquée de façon **incohérente** : classification filesystem exhaustive des 22 chemins `.py` du doc → **5 entrées pyc-only NON annotées** (source absente au chemin exact indiqué, seul le `.pyc` présent) : L20 `oto_module_etudes_faisabilite_real`, L21 `oto_module_execution_faisabilite`, L22 `oto_module_faisabilites_pages`, L23 `oto_module_faisabilites_viewer`, L59 `oto_module_mobile_api`. **Correctif : ajout de « (compilé pyc) »** sur ces 5 lignes — alignement sur la **convention propre du doc** + la **réalité filesystem vérifiée** (strictement analogue au fix `financement_bancaire`/`2e4ff2d` qui alignait une docstring sur la convention de ses pairs). **Zéro invention.** Assets `.py` avec source réelle (`aec.py`, `knowledge.py`, `prompt_engine.py`, `otoia_batch_projets.py`, `gen_renders_flux.py`, `mobile_download.py`, `seed_mobile_rbac.py`, `confotur_application.py`, `bim_compras.py`, `ifc_to_glb.py`, `worker_tasks_queue.py`, `executive_hero_flux.py`) = laissés sans annotation (correct). **⚠️ Deux items SURFACÉS à Michel (NON édités — contredisent une source d'autorité / non-réfutables) :** 1. **`otoia/capabilities/chat.py` (L102) est ABSENT** au chemin indiqué (ni `.py` ni `.pyc`), **alors que CLAUDE.md le liste comme capability OTOIA canonique** (« aec.py + knowledge.py + prompt_engine.py + chat.py »). Incohérence doc-inventaire ↔ constitution → je **ne réécris pas** l'inventaire pour contredire CLAUDE.md ; **à trancher par Michel** (chat.py à recréer, ou capability à retirer de la liste canonique ?). Note : le footer L128 du même doc **omet déjà** chat.py (« aec + knowledge + prompt_engine » seulement) — inventaire déjà auto-incohérent sur ce point. 2. **`config/projets_editor.py` (L127) « (API GET/POST déjà en place)»** — source absente au chemin, **mais** le claim porte sur une **API VPS live**, non réfutable par la seule absence de source in-`/opt/oto` (peut tourner déployée ailleurs) → **non touché**, signalé pour confirmation. **Gate.** `AGENTS_EXISTING_ASSETS.md` est référencé par `ci/check_readme_claims.sh` **uniquement** pour l'endpoint RunPod (§1) et des titres — **aucune annotation `(compilé pyc)` n'est pinnée**. `./run_ci.sh` en clôture = **32 PASS · 0 FAIL · 0 SKIP** (inchangé). **Réalisé.** 1 fichier prose (5 lignes annotées, convention + réalité). 2 items surfacés à Michel. Classe inventaire-vs-filesystem = **nouvelle**, distincte de docstring-vs-CODE et README-counts ; reste **ouverte** pour les claims filesystem des docs racine. ## Session `20260803_140718` · Sprint 8 QA · inventaire-vs-filesystem étendu aux assets **non-`.py`** d'`AGENTS_EXISTING_ASSETS.md` — CLEAN (aucune édition) + signal chat.py enrichi **État d'entrée.** `./run_ci.sh` = **32 PASS · 0 FAIL · 0 SKIP**, arbre `git status` propre. Roadmap 1→8 intégralement livrée + gatée. Recherche de la prochaine tâche prioritaire **non-complétée in-périmètre** : (a) recherche de tâche **fonctionnelle** (Michel · « commits fonctionnels ») → `grep TODO/FIXME/stub/NotImplemented` sur les 12 générateurs + `05_deliverables_mvp/*.py` = **0 stub réel** (tous les hits « placeholder » sont le vocabulaire **anti-invention** légitime — sentinelles `{{…}}`/`USER_PLACEHOLDER`, pas du code inachevé) ; les 12 modules ont leur `*_gen.py`. Zéro tâche fonctionnelle in-scope ne reste (constat partagé par les sessions antérieures ; le reste vise le runtime VPS hors périmètre #8). → bascule sur la **seule surface de dérive encore fraîche** : la classe [[inventory-vs-filesystem-drift]] ouverte par `133718`, qui n'avait classifié que les **22 chemins `.py`** du doc. **Surface neuve auditée (jamais couverte).** Les **claims d'assets NON-`.py`** locaux d'`AGENTS_EXISTING_ASSETS.md` (`.md`/`.json`/`.sh`/`.js`/`.safetensors`/`.pristine`) — 12 chemins sous `/opt/oto`, vérifiés **un par un** au filesystem réel (jamais confiance aveugle) : - **11 = SRC présents** (source réelle au chemin exact) → **aucune annotation `(compilé pyc)` due** (ces formats ne compilent pas en pyc) : `faisabilite_4_volets_standard.md`, `flux1-dev-fp8.safetensors`, `mission_seo_indexation.md`, `task_seo_indexation_20260727.json`, `seo_indexation_20260727_INITIAL.md`, `deploy_mobile_rbac.sh`, `seed_mobile_rbac.py`, `static/oto_mobile.js`, `oto_module_confotur_application.py` (+ `.pristine`), `config/projets_config.json`. - **1 = MISS** : `config/projets_editor.py` — **déjà surfacé** à Michel par `133718` (item 2 : claim d'**API VPS live**, non-réfutable par la seule absence de source in-`/opt/oto`) → **non touché**. **Résultat : 0 dérive, 0 édition.** La classe [[inventory-vs-filesystem-drift]] est désormais **close pour les assets LOCAUX** (22 `.py` traités par `133718` + 11 non-`.py` vérifiés SRC ici) ; elle ne reste ouverte que sur les claims **VPS-externes / non-réfutables** (API `projets_editor`, tailles de fichiers data-room). Re-scanner les assets locaux serait désormais redondant (#5). **Signal chat.py ENRICHI à Michel (item 1 de `133718`, non tranché).** Re-confirmé : `otoia/capabilities/chat.py` **absent** (ni `.py` ni `.pyc`) alors que CLAUDE.md le liste comme capability OTOIA canonique. **Nuance neuve** : un `__pycache__/oto_module_chatbot_lead.cpython-310.pyc` **existe** — mais c'est un **module chatbot-lead distinct**, PAS la capability `capabilities/chat.py`. Donne à Michel le contexte pour trancher (recréer `chat.py`, ou pointer la capability vers le module lead existant, ou retirer de la liste canonique). **Toujours non édité** — décision → Michel (ne pas réécrire l'inventaire contre la constitution CLAUDE.md). **Sûreté.** Session **audit + log pur** : `AGENTS_EXISTING_ASSETS.md` **non modifié** (0 dérive locale trouvée). Aucun artefact `out/` touché, aucun claim gaté, aucune logique de prod/gate. `./run_ci.sh` en clôture = **32 PASS · 0 FAIL · 0 SKIP** (inchangé). **Zéro nouveau module · zéro gate ajouté (#5).** Aucune commande touchant au VPS (#8) · aucun `git clean` . ## Session `20260803_143719` · Sprint 8 QA · audit **count-drift dans les surfaces UNGATED** (`daily_reports/` + prose gate-docs `ci/`) — CLEAN (0 dérive, 0 édition) + signal chat.py resserré **État d'entrée.** `./run_ci.sh` = **32 PASS · 0 FAIL · 0 SKIP**, arbre `git status` propre. Roadmap 1→8 livrée + gatée ; file de tâches fonctionnelles in-scope vide (constat partagé `130717`/`140718` — reste = runtime VPS hors #8). Classe inventaire-vs-filesystem **close pour les assets locaux** (`140718`). → cible d'audit : la **seule classe de dérive silencieuse encore possible** = les **chiffres de comptage** (24 modules · 624 tests · 32 gates) dans les surfaces **explicitement HORS gate**, où toute dérive passerait inaperçue. **Vérité-terrain re-dérivée (recompute `python3` indépendant · aucun chiffre saisi · #6).** `quality_report.json` → **24/24 modules à 100/100** (min=max=100, `pass_score` 95) · `regression_run.json` `totals` → **24 suites · 624 ran · 624 passés · 0/0/0** · `acceptance_matrix.json` → **15/15** promesses `in_repo` (verdict `True`) · `.gitea/workflows/ci.yml` → `gate.needs` = **32** jobs · `run_ci.sh` → **32 PASS**. **Surface 1 — piste écartée après vérification : le canal `daily_reports/` n'est PAS périmé.** Hypothèse d'entrée : le rapport `daily_reports/2026-08-03.md` (en-tête session `000623`) affiche « 22/22 modules · 564 ran · 30 PASS » dans sa table « État courant MVP » (L54) — antérieure à la réconciliation 22→24 (`e24f2c9`, session `100714`). **MAIS** lecture du fichier **entier** : la session `100714` a **déjà** annexé une table « État launch-readiness · addendum `100714` » (L250-281) portant les chiffres courants **24/624/32** — l'addendum **supersède** la table d'en-tête par la **convention** du canal (chaque `daily_report` = **snapshot daté immutable** ; addenda empilés — le `2026-08-02` en compte 7). Convention **ancrée dans le code du gate** : `ci/check_docs.sh:50` (« rapports : hors gate score ») + `ci/check_readme_claims.sh:8914` (« daily_reports/activity_log = snapshots datés »). → **Fabriquer un nouvel addendum ne ferait que dupliquer `100714`** (workflow #5, « JAMAIS accumuler doublons »). **Non touché.** Grep élargi des chiffres « 22/564/30 » sur tous les `.md` trackés : **tous les hits résiduels sont dans `2026-08-02.md`** — exacts pour **ce jour-là** (les 2 modules neufs sont arrivés le 08-03), donc **snapshots datés corrects**, pas de la dérive. **Surface 2 — résidus « 22 »/« 564 » dans la prose des gate-docs `ci/` = pédagogiques, KEEP.** 8 hits restants (`ci/README.md` L197/227/729 · `ci/check_regression.sh` L19 · `ci/check_readme_claims.sh` L14/653/791/3164 · `ci.yml` L109). Classifiés un par un selon la règle [[prose-facts-vs-numeric-drift]] (*historique-repro / pédagogique-hypothétique / cite-de-ligne = KEEP · présent-état-courant = FIX*) : **tous pédagogiques/historiques** — scénarios d'échec illustratifs (« ex. 21→22 modules », « 22 suites devenu faux si… », « bijective 22/22 vs CI : vert trompeur ») qui expliquent **ce que le gate attrape**. Cas le plus ambigu, `ci/README.md:227` « Le compte agrégé ci-dessus (« 564 tests ») ne suffit pas » : c'est un **paragraphe de rationale figé** où « 564 » cohabite avec ses frères narratifs **534** (L216), **560** (L220), **31/37/35→34/25→26** (L226-227) — tous des exemples d'incidents de dérive **passés**. Bumper **le seul** 564→624 en laissant 534/560 rendrait la narration **auto-incohérente**. `git show f9d303f` confirme : la réconciliation 22→24 des gate-docs a **délibérément laissé** ce 564 (son diff ne touche que la citation **vive** « 22 modules »). → règle « **don't blanket-bump** » respectée. **Non touché.** **Signal chat.py RESSERRÉ à Michel (item ouvert `133718`/`140718`, non tranché).** Re-vérifié au filesystem : `/opt/oto/otoia/capabilities/chat.py` **absent**, **mais** les **3 autres** capabilities canoniques de CLAUDE.md **existent comme source `.py` réelle** (`aec.py`, `knowledge.py`, `prompt_engine.py` — `ls` confirmé). **Nuance neuve** : ce n'est **pas** un répertoire pyc-only ni un gap de build — c'est un **gap d'une seule capability sur quatre**. Contexte affiné pour la décision de Michel (recréer `chat.py` · pointer vers `frontend/chat_otoia/` livré in-repo · ou retirer de la liste canonique). **Toujours non édité** (ne pas réécrire l'inventaire/la constitution). **Résultat : 0 dérive, 0 édition de prod/doc.** Session **audit + log pur**. Aucun artefact `out/` touché, aucun claim gaté, aucune logique de gate. La classe **count-drift** est désormais vérifiée **close jusque dans les surfaces UNGATED** (daily_reports = snapshots datés corrects + superséd. par addendum ; prose `ci/` = résidus pédagogiques délibérés). Re-scanner ces surfaces serait redondant (#5). `./run_ci.sh` en clôture = **32 PASS · 0 FAIL · 0 SKIP** (inchangé). **Zéro nouveau module · zéro gate ajouté (#5) · zéro chiffre inventé (#6).** Aucune commande touchant au VPS (#8) · aucun `git clean` . ## Session `20260803_150719` · Sprint 8 QA · audit **constantes de marque (hex + polices) sur TOUT le corpus `.md`** — extension de la classe [[claude-md-constant-anchor-gate]] au-delà des 2 surfaces gatées — CLEAN (0 dérive, 0 édition) **Hypothèse d'entrée.** Le gate `claude-md-constant` (cf. `check_readme_claims.sh:6869-6883`) ré-dérive la paire canonique `#0a0a12` + `#f0b429` / `Fraunces` + `Cormorant Garamond` **uniquement** sur `branding.py` (docstring + oracle de test) et `README.md:43`. Or ces constantes sont **retranscrites dans ~18 fichiers `.md`** (specs, fiches AGENT, directives, `OTO_DESIGN_SYSTEM_v1.md`, `MASTER_PROMPT`). Surface potentiellement **UNGATED** → cible d'un hex/police faux qui passerait vert. Angle **neuf** (les sessions récentes ont clos count-drift, docstring-vs-code, inventaire-vs-filesystem ; jamais un balayage des constantes de design sur l'ensemble du corpus doc). **Vérification 1 — claim « 13 agents » (adjacent, souvent co-localisé au branding).** Re-compté : `ls -d 03_agents/*/ | wc -l` = **13** (exact). Grep du claim sur tous les `.md` : les surfaces **vives** (`README` ×2, `MASTER_PROMPT_DTP_v2.md:28`, titre `AGENTS_EXISTING_ASSETS.md`) sont **déjà gatées** — `check_readme_claims.sh:241` inclut explicitement `02_master_prompt/MASTER_PROMPT_DTP_v2.md` dans `rp_files`, doc dans `ci/README.md:208-210`. **Aucune surface `13 agents` non couverte.** (Confirme [[verify-uncovered-before-gating]] : la couverture est dense — pas de gate à ajouter, #5.) **Vérification 2 — énumération EXHAUSTIVE des hex sur le corpus `.md`.** `grep -rhoE '#[0-9a-fA-F]{6}'` (hors `.git`) → la **paire canonique domine** (`#f0b429` ×27 · `#0a0a12` ×23). Tous les autres hex classés **membres légitimes de la palette étendue** définie par `OTO_DESIGN_SYSTEM_v1.md`, **pas** des variantes dérivées de la paire mandataire : `#25D366` (vert WhatsApp — canal intégration CRM) · `#FAF8F4`/`#faf8f4` (crème fond clair) · `#C9A465`/`#c9a227`/`#e6c17f` (déclinaisons or secondaire) · `#0A0A0E` (near-black distinct) · `#ef4444`/`#3b82f6`/`#6b7280` (sémantiques rouge/bleu/gris UI) · `#f5b04a`/`#ffd66a` (bronze logo, cf. `OTO_DESIGN_SYSTEM_v1.md:208`) · `#e0a420` (= **l'exemple de mutation cité par le gate lui-même**, `check_readme_claims.sh:6883` « `#f0b429`→`#e0a420` »). **Aucun hex ne prétend être la constante mandataire tout en s'en écartant** → 0 dérive. **Vérification 3 — polices.** Corpus cohérent : `Cormorant Garamond` (titres) · `Fraunces` (chiffres élégants / titres droits « OTO ») · `Playfair Display`+`Georgia` (fallbacks) · `Inter` (corps/UI). **Nuance neuve, NON une dérive** : `OTO_DESIGN_SYSTEM_v1.md:52,56` assigne des **rôles** (« Titres majeurs · Cormorant Garamond », « Chiffres · Fraunces ou Cormorant ») là où CLAUDE.md #4 énonce la **paire** « Fraunces + Cormorant Garamond ». Les **deux polices sont présentes** dans les deux docs ; le design-system **élabore** la répartition — c'est le doc **autorité de design** qui détaille, pas une contradiction de la constitution. De plus `OTO_DESIGN_SYSTEM_v1.md` est **REFERENCE root-owned** (cf. session `083713`) → « surface, don't overwrite ». **Non touché.** **Résultat : 0 dérive, 0 édition.** La classe **constantes-de-marque** est vérifiée close **au-delà** des 2 surfaces gatées (`branding.py` + `README:43`) : les ~18 retranscriptions `.md` sont soit déjà gatées (13 agents), soit des membres de palette légitimes (hex), soit une élaboration de rôle par le doc autorité (polices). Re-scanner serait redondant (#5). `./run_ci.sh` en clôture = **32 PASS · 0 FAIL · 0 SKIP** (inchangé). **Zéro nouveau module · zéro gate ajouté (#5) · zéro chiffre inventé (#6).** Aucune commande touchant au VPS (#8) · aucun `git clean` . ## Session `20260803_160723` · Sprint 8 QA · triple-sonde drift réel (docstring-vs-CODE · bijection module↔artefacts · count-drift présent-tense UNGATED) — CLEAN (0 dérive, 0 édition) **Angle d'entrée.** Plutôt qu'un énième re-scan d'une surface déjà saturée, sonder **trois** classes de dérive **réelle** (celles qui, historiquement, ont produit de vrais fix : `docstring-vs-code-drift`, réconciliation de compte 22→24 de la session `100714`) et vérifier qu'elles restent closes après la dernière évolution de code. État de départ vert : `./run_ci.sh` = **32 PASS · 0 FAIL · 0 SKIP** · `git status --porcelain` **vide**. **Sonde 1 — docstring/README-vs-CODE sur les 22 générateurs (`*_gen.py` + README frère).** Confrontation systématique des claims « Entrée/Sortie/Fallback/Invariants » de chaque docstring **et** README à ce que le code **écrit/lit réellement** (`_write_json`, `.write_text`, listes `_ARTIFACTS`). Exemples recoupés : `demo_scenario_gen` écrit bien `run_sheet.json` + `run_sheet.md` + `MANIFEST.json` (l.197-204, concorde docstring l.19-20 + README l.76-78) ; `financement_bancaire` = **7** artefacts (`_ARTIFACTS` l.57-65 == « 7 fichiers » docstring l.29 + README l.90) ; `seo_gen` 4 sorties concordantes (l.259-262). **0 sortie fantôme, 0 sortie non documentée.** Classe non close par nature (`docstring-vs- code-drift`) mais **actuellement saturée** — cohérent avec le sweep `1c3bed3` d'hier. **Sonde 2 — bijection module↔artefacts (classe du vrai drift 22→24 de `100714`).** Tentative de recouper indépendamment {répertoires à générateur} vs {modules `quality_report`} vs {suites `regression_run`} vs {`gate.needs`}. **Constat neuf** : cette bijection est **déjà AUTO-gatée** par le bloc `coverage` de `qa/audit_4big/out/quality_report.json` — `ci_modules_count=24` · `registry_modules_count=24` · `missing_in_registry=[]` · `missing_in_ci=[]` · `not_in_gate=[]` · `self_module_excluded="qa/audit_4big"`. Le générateur `audit_4big` recalcule la couverture à chaque build et la byte-repro (`check-artifacts`) prouve **transitivement** l'intégrité. → **Aucun gate à ajouter (#5)**, c'est exactement le filet qui, aujourd'hui, empêche la récidive du drift `100714`. **Sonde 3 — count-drift présent-tense dans les surfaces UNGATED, post-réconciliation 22→24 / 569→624 tests.** Grep des empreintes périmées (`22 suites`, `22/22 modules`, `569`, `23/23`) hors `daily_reports/`+`activity_log/`. 3 hits, **chacun classé** (méthode `f9d303f` — classer, ne jamais blanket-bump) : - `faisabilite/bancable/README.md:105` « **22/22 verts** (finance, rendu…) » = compte de tests **LOCAL au module bancable** (recompté : `Ran 22 tests · OK`), **pas** le compte de modules — collision lexicale fortuite avec l'ancien « 22/22 modules ». **ACCURATE, KEEP.** - `ci/README.md:197` « (21→22 modules, 21→22 suites) » = exemple **pédagogique-hypothétique** du mécanisme de dérive silencieuse (« Quand un module + son job CI sont ajoutés… »). **KEEP.** - `ci/README.md:729` « bijective 22/22 vs CI … aurait sauté 2 » = narration **historique** d'un défaut réel reproduit-puis-corrigé (« 20 modules listés sur 22 »). **KEEP.** **Résultat : 0 dérive, 0 édition de prod/doc.** Session **vérification pure** sur 3 angles genuinement sondés — tous CLEAN. Aucun artefact `out/` touché, aucun claim gaté modifié, aucune logique de gate ajoutée (#5). La bijection module↔artefacts est confirmée **auto- gatée** (filet anti-récidive du drift `100714`) ; la classe count-drift UNGATED reste close (3 résidus tous légitimes) ; docstring-vs-CODE saturée. `./run_ci.sh` en clôture = **32 PASS · 0 FAIL · 0 SKIP** (inchangé). **Zéro nouveau module · zéro gate ajouté (#5) · zéro chiffre inventé (#6).** Aucune commande touchant au VPS (#8) · aucun `git clean` . ## Session 163724 · Audit NEUF · directive-vs-implémentation (Michel input-specs `DIRECTIVE_*.md` → module) sur `financement_bancaire` → CLEAN 0 édition · classe caractérisée + transitivement protégée **Contexte + choix de tâche.** `./run_ci.sh` au démarrage : **32 PASS · 0 FAIL · 0 SKIP**, arbre propre. Les ~9 sessions précédentes (`160723`→`130717`) sont **CLEAN 0-édition** : docstring-vs-CODE, bijection module↔artefacts, count-drift présent-tense, constantes de marque #4, noms d'entités/projets — toutes **saturées** (re-scanner = redondant #5). J'ai ouvert une surface **jamais indexée dans MEMORY.md** : les **8 fichiers `DIRECTIVE_*.md`** (specs d'ENTRÉE authored par Michel, non-DTP-Worker) confrontés à l'implémentation du module qu'ils pilotent. Cible = `DIRECTIVE_FINANCEMENT_BANCAIRE_COMPLET` (15 KB, le plus riche en paramètres numériques concrets) vs `crm/financement_bancaire/`. **Cross-check paramètre par paramètre — tout concorde.** - **Taux d'apport 20 % résident RD / 30 % étranger** (directive l.150-151, l.158) → **mono- sourcés** dans `financement_spec.json` (`residence_types[*].taux_apport_pct` = 20/30), lus par `finlib/gate.apport_requis_usd`. Aucune re-transcription du 20/30 en dur ailleurs (Invariant 2 gen re-vérifie `0 < résident < étranger ≤ 100`). ✅ - **4 conditions de gate** (bannière directive l.168-173 : dépôt initial · tous documents · toutes autorisations · validation manuelle WAG) → module `CONDITION_KEYS = (apport_initial_complet, documents_exiges, autorisations_signees, validation_wag)`, **gatées par Invariant 7** (« exactement 4 conditions, clés == CONDITION_KEYS, ordre 1..4 »). Le « 4 conditions » du README/gen/gate.py est donc déjà anti-dérive. ✅ - **4 banques du directive** (l.21-25) fidèlement encodées dans le spec → Banreservas (25 ans · LTV 80 %) · Popular (LTV 75-85 %) · BHD León (30 ans · étrangers · bilingue) · Scotiabank (LTV 70 % · devise USD). Recoupé champ par champ (`ltv_min/max_pct`, `duree_max_ans`, `specialites`, `devises`). ✅ - **2 banques EN PLUS du directive** (Santa Cruz, López de Haro) : tous chiffres à `null` + `note` « à confirmer (#6) ». Enrichissement **honnête** — zéro chiffre inventé, exactement la posture anti-invention #6 (les valeurs manquantes des 4 banques directive sont elles aussi `null`+« à confirmer », p.ex. taux exacts Banreservas). ✅ - **Ratio d'endettement < 40 %** (directive l.221) : appartient à l'**agent OTO Auditeur Finances** (directive §217-260, sortie runtime `/opt/oto/data/finance-audits/`, signature HMAC) — concern **séparé, hors ce module et hors périmètre worker VPS**. **Pas une dérive.** **Protection transitive confirmée.** `python3 financement_bancaire_gen.py` régénéré → `git status --porcelain out/ financement_spec.json` **vide** = `out/` byte-identique (repro prouvée par `check-artifacts`). Le spec est la **source d'autorité** du module ; le directive daté `20260803` est un **snapshot d'intention** (comme `daily_reports/`) qui *flue dans* le spec, pas un oracle vivant. **Aucun gate directive↔spec à ajouter (#5)** : ce serait figer le spec à un doc daté supersédable (même logique que « ne pas gater sur les snapshots datés »). **Résultat : 0 dérive, 0 édition (prod/doc/gate).** Classe NEUVE `directive-vs- implementation` caractérisée et **CLEAN sur son échantillon le plus dense** ; les paramètres critiques (20/30 · 4-conditions · banques) sont déjà transitivement protégés (spec byte-gaté + Invariants 2/7). `./run_ci.sh` en clôture = **32 PASS · 0 FAIL · 0 SKIP** (inchangé). Zéro module créé · zéro gate ajouté (#5) · zéro chiffre inventé (#6). Aucune commande VPS (#8) · aucun `git clean` . ## Session `20260803_170724` · Sprint 8 QA · directive-vs-implémentation, 2e couple (`DIRECTIVE_MOBILE_STORES` → `mobile/app_config`) **But.** Étendre la classe d'audit NEUVE [[directive-vs-implementation]] (caractérisée session `163724` sur `financement_bancaire`) à un second couple directive↔module : la `DIRECTIVE_MOBILE_STORES_20260803.md` (input-spec daté de Michel) vs le module qu'elle pilote, `05_deliverables_mvp/mobile/app_config/`. **Intégrité gatée d'abord — verte.** `app_config_gen.py build` puis `validate` → `git status --porcelain out/ mobile_spec.json` **vide** (out/ byte-identique, repro prouvée par `check-artifacts`) · `✅ Validation OK — 5 onglets, 44 rôles, schéma + 15 invariants verts`. `./run_ci.sh` = **32 PASS · 0 FAIL · 0 SKIP** (inchangé). Aucune dérive gatée. **Deux couches de marque distinctes (existant runtime vs cible rebuild) — PAS une dérive.** Même dédoublement que [[project-brand-vs-codename-not-drift]] et surtout [[mobile-runtime-actual-vs-rebuild-target]] : - Directive = **app EXISTANTE** (`/opt/oto/mobile/native/`, audit runtime) : nom `OTOV7`, iOS/Android `com.otov7.app`, Expo SDK **51.0.0**. - Spec = **cible de REBUILD** : `name` « OTO Enterprise OS » (source = titre CLAUDE.md), `slug` « oto-enterprise-os », `scheme` « otoos », `expo_sdk_major` **54** (source = roadmap l.56 « Rebuild Expo 54 »). Les deux couches sont **légitimes et séparées** ; le spec ne prétend nulle part que le runtime est déjà en 54 / renommé — il porte la CONFIG versionnable de la cible. Décision Expo 51→54 (directive P0) déjà couverte par [[mobile-runtime-actual-vs-rebuild-target]]. **CLEAN.** **Identifiants de store `null · a_confirmer` — par design, pas une dérive.** Le spec null'e `bundleIdentifier`/`package`/`version`/`projectId`/credentials avec raison #8 (« déposé hors repo lors des builds/submissions ») — exactement la posture anti-invention #6/#8, même logique que les `null`+« à confirmer » de `financement_bancaire`. Aucun chiffre fabriqué. ✅ **⚠️ SIGNAL à Michel (tension RÉELLE, à SURFACER — pas de réécriture).** Le `a_confirmer` de `app_config.expo.ios.bundleIdentifier` porte la raison « identité App Store #32 — **jamais fabriquée** (#6/#8) ». Or la directive documente cet identifiant comme **déjà connu et existant** : `com.otov7.app` (l.15-16). Sur Apple, le **bundle identifier d'une app publiée est immuable** : l'« App Store #32 » ciblée par la roadmap l.56 existe déjà sous `com.otov7.app`. Donc cette valeur n'est pas « à fabriquer / inconnue » — c'est un identifiant **fixe existant qui DOIT être réutilisé** pour *mettre à jour* l'app #32. Le renommage `OTOV7` → « OTO Enterprise OS » + le changement de `slug`/`scheme` **compose** ce risque : si le rebuild adopte un nouveau bundle id, il devient une **NOUVELLE app** (nouvelle fiche), pas une mise à jour de #32. **Question produit à trancher par Michel** : le rebuild met-il à jour l'app #32 (⇒ réutiliser `com.otov7.app` + garder la fiche) ou crée-t-il une nouvelle fiche ? **Pourquoi ne PAS éditer.** Le spec est la **source d'autorité** du module ; la directive datée `20260803` est un **snapshot d'intention** (comme `daily_reports/`), pas un oracle vivant → **aucun gate directive↔spec (#5)**. Garder le bundle id `null` dans la config versionnable est **légitime (#8** : la valeur concrète est déposée hors repo au build). La nuance de wording du `raison` (« jamais fabriquée » vs « existant à réutiliser ») est trop fine pour justifier de figer un spec piloté-par-snapshot ; c'est la **décision de continuité de fiche** qui mérite l'arbitrage explicite de Michel, pas une réécriture de doc. **Résultat : 0 dérive gatée, 0 édition (prod/doc/gate).** Classe [[directive-vs-implementation]] étendue au **2e couple** (mobile) = **CLEAN** + **1 signal de continuité produit** surfacé à Michel (bundle id `com.otov7.app` immuable ↔ App Store #32). Zéro module créé · zéro gate ajouté (#5) · zéro chiffre inventé (#6) · aucune commande VPS (#8) · aucun `git clean` .