Files
Claude Code DTP Worker 503f726ae7 [DTP-Worker 20260803_200738] Re-vérif indépendante couple PLANPOINT↔spec (2 contradictions confirmées RÉELLES) + rattrapage canal daily_reports (3 sessions) · 0 édition prod
Post-fermeture du cycle « audit 8 directives » (88bd025), plutôt qu'un 9e audit
redondant : re-vérification indépendante de la dernière édition substantielle
(encart Arbitrage inséré dans specs/CHOISIR_MON_UNITE_SPEC par a9e9ee9), point le
plus susceptible d'abriter un défaut résiduel.

VÉRIF · les 2 contradictions de l'encart sont CONFIRMÉES RÉELLES (grep croisé
directive↔spec) :
 · Décision 4 Signature : spec=DocuSign (SaaS externe, l.58/89/112) ↔ directive §7
   l.53=OTO Sign™ (natif)
 · Décision 5 Comparateur : spec=NON (l.59/90/119) ↔ directive §6 l.48/86=max 3 unités
→ aucun faux-positif inséré · édition a9e9ee9 factuellement saine · 0 correction.

SIGNAL Michel ENRICHI (nouvel angle, non tranché) · la Décision 4 n'est pas neutre
vis-à-vis du mandat : CLAUDE.md #1 = « ERPNext natif priorité absolue avant tout
outil externe » → OTO Sign™ (directive/natif) plus conforme que DocuSign (spec/SaaS
externe) ; directive aussi la + récente (08-03 vs 07-28) → récence ET conformité #1
pointent vers OTO Sign™. NON flippé (spec=autorité, #5).

RÉALISÉ · rattrapage daily_reports/2026-08-03.md (s'était arrêté à 173726, 3 sessions
de retard : 180728/190730/193734/200738). Chiffres 100% re-dérivés d'artefacts
committés : 24/24 qualité (quality_report.coverage) · 624/624 régression
(regression_run.totals) · 15/15 recette (acceptance_matrix) · 32 gate (run_ci).
Canal gate-neutre (hors out/ byte-gaté, hors audit_4big/check_artifacts), même
posture que 063711.

run_ci 32 PASS · 0 FAIL · 0 SKIP inchangé · 0 fichier prod touché · 0 commande VPS (#8).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-03 20:12:46 +00:00

1717 lines
131 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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` <!-- ci-allow : mention de l'interdit, jamais exécuté -->.
## 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` <!-- ci-allow : mention de l'interdit absolu, jamais exécuté -->.
## 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/` <!-- ci-allow : citation de la cause, jamais un usage --> (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/` <!-- ci-allow : citation de l'inconsistance signalée --> 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` <!-- ci-allow : mention de l'interdit absolu, jamais exécuté -->.
## 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` <!-- ci-allow : mention de l'interdit absolu, jamais exécuté -->.
## 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é <!-- ci-allow : mention de l'interdit absolu, jamais 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` <!-- ci-allow : mention de l'interdit absolu, jamais exécuté -->.
## 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` <!-- ci-allow : mention de l'interdit absolu, jamais exécuté -->.
## 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) » <!-- ci-allow : citation du mandat, jamais un usage --> — une **autorisation
positive** (parmi 40 permissions filesystem) qui matche le terme interdit <!-- ci-allow -->
`/var/www/html/static/` **sans** marqueur de prohibition <!-- ci-allow --> (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/` » <!-- ci-allow : citation, contradiction signalée --> alors que
**CLAUDE.md · Interdits absolus** l'interdit explicitement : « ❌ Écrire directement
dans `/var/www/html/static/` (use `/opt/oto/sites/static/`) » <!-- ci-allow : citation de l'interdit CLAUDE.md --> — 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` <!-- ci-allow : mention de l'interdit absolu, jamais exécuté -->.
## 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/`),
`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` <!-- ci-allow : mention de l'interdit absolu, jamais exécuté -->.
## 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` <!-- ci-allow : mention de l'interdit absolu, jamais exécuté -->.
## 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` <!-- ci-allow : mention de l'interdit absolu, jamais exécuté -->.
## 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` <!-- ci-allow : mention de l'interdit absolu, jamais exécuté -->.
## 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` <!-- ci-allow : mention de l'interdit absolu, jamais exécuté -->.
---
## 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` <!-- ci-allow : mention de l'interdit, jamais exécuté -->.
## 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 = 134, 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` <!-- ci-allow : mention de l'interdit absolu, jamais exécuté -->.
## 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` <!-- ci-allow : mention de l'interdit absolu, jamais exécuté -->.
## 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` <!-- ci-allow : mention de l'interdit absolu, jamais exécuté -->.
## 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` <!-- ci-allow : mention de l'interdit absolu, jamais exécuté -->.
## 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` <!-- ci-allow : mention de l'interdit absolu, jamais exécuté -->.
## 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` <!-- ci-allow : mention de l'interdit absolu, jamais exécuté -->.
---
## Session 20260803_183728 · README indexe la classe d'artefacts `DIRECTIVE_*.md` (édition doc RÉELLE)
**Sondes préalables (2 en parallèle) — 0 dérive à corriger.**
1. **Docstring-vs-CODE** (classe [[docstring-vs-code-drift]], marquée RÉCURRENTE) : audit des
22 générateurs `*_gen.py` + helpers `*lib*.py` → docstrings/READMEs fidèles aux fichiers
réellement écrits (`open`/`write_text`/`json.dump`) et aux clés lues. **Aucun phantom
d'input/fallback/output.** Les dérives passées (publiciste « fallback prix §5.3 », demo
`run_sheet.md`, ecf « 12 invariants ») restent corrigées.
2. **Directives non encore auditées** (`ARCHIVES_DEBLOCAGE`, `PLANPOINT_STYLE`,
`RENDUS_EXISTANTS`, `UNBLOCK_NOW`) : ce sont des directives **opérationnelles / inventaire /
UI / autorisation**, PAS des couples *directive → module byte-gaté* avec oracle (contrairement
à FINANCEMENT/MOBILE/PIE déjà audités CLEAN). Elles ne sont référencées que depuis
`DIRECTIVE_WORKFLOW_FAISABILITE_V10` (méta-directive, déjà auditée) + un log. Rien à gater.
**Le gap RÉEL trouvé (et corrigé).** Les **8 fichiers `DIRECTIVE_*.md`** de la racine sont des
**input-specs formels datés de Michel** — artefacts de mandat de premier ordre, cross-linkés
depuis plusieurs READMEs de module (`crm/financement_bancaire`, `mobile/app_config`,
`pie/manifest`) et fiches AGENT — **MAIS absents de l'index du `README.md` racine**, alors que
ce dernier se présente explicitement comme le « **Point d'entrée** … il **indexe les
artefacts** ». `grep "DIRECTIVE" README.md` = **0 occurrence**. Classe d'artefacts entièrement
invisible depuis le point d'entrée.
**Édition (README.md, doc réelle, PAS un signal-only) :**
- **Ligne de navigation** ajoutée (après `PORTAIL_BANCABLES_4BIG.md`) pointant vers une ancre
intra-doc `#directives-michel-input-specs-datés` (ancre non validée par `check_docs` qui
strippe `#…` l.33 → sûr).
- **Nouvelle section `## Directives Michel (input-specs datés)`** (avant `## Git`) : cadre la
nature snapshot (`spec = authority`, comme `daily_reports`), rappelle l'exclusion de
`guard_constraints` (gouvernance citant URLs/chemins interdits), puis **liste les 8
directives en 2 groupes honnêtes** :
- **Pilotent un module byte-gaté** (audité *directive→implémentation*) : FINANCEMENT →
`crm/financement_bancaire` · MOBILE_STORES → `mobile/app_config` · PIE → `pie/manifest`.
- **Contexte/inventaire/workflow/gouvernance** (pas de module 1:1, runtime/VPS hors
périmètre #8) : WORKFLOW_FAISABILITE_V10 · ARCHIVES_DEBLOCAGE · RENDUS_EXISTANTS ·
PLANPOINT_STYLE · UNBLOCK_NOW.
**Discipline anti-drift respectée.** **Aucun compte agrégé en dur** (« 8 directives »/« 4 »)
introduit → pas de scalaire qui dérive en silence (#6) ; l'énumération EST la liste. Tous les
liens ciblent des fichiers **existants** (`check_docs` HARD vérifie l'existence). Groupement
**vérifié sémantiquement** (V10 n'a PAS de module 1:1 → classé contexte, pas faux-lié ;
`faisabilite/AGENT.md` référence en réalité PIE, pas V10). Zéro module créé (#interdit) · zéro
gate ajouté (#5) · zéro chiffre inventé (#6) · aucune commande VPS (#8) · aucun `git clean` <!-- ci-allow : mention de l'interdit absolu, jamais exécuté -->.
**Vérification :** `run_ci.sh --static` → **7/7 PASS** (dont `check-docs` liens OK +
`check-readme-claims` vert : aucune nouvelle assertion chiffrée). Baseline pré-édition
`run_ci.sh` complet = 32 PASS ; édition README-seule → suites de module inchangées.
**Résultat : 1 édition doc RÉELLE** (README indexe enfin la classe `DIRECTIVE_*.md`), classe
[[directive-vs-implementation]] rendue **navigable depuis le point d'entrée**. Gate vert.
## Session 20260803_190730 · Audit directive-vs-SPEC (couple NEUF) → édition doc RÉELLE + SIGNAL Michel
**Classe [[directive-vs-implementation]] étendue à un couple encore non audité :
`DIRECTIVE_PLANPOINT_STYLE_20260803.md` (2026-08-03) ↔ `specs/CHOISIR_MON_UNITE_SPEC.md`
(DÉCISIONS FINALES 2026-07-28).** Jusqu'ici seuls FINANCEMENT / MOBILE / PIE / V10 étaient
triangulés ; PLANPOINT était classé « contexte/UI, pas de module byte-gaté » et n'avait donc
jamais été confronté à sa **spec** de destination (qui, elle, existe : `specs/CHOISIR_MON_UNITE_SPEC.md`).
**Sondes préalables — 3 classes réelles, toutes CLEAN (0 à corriger) :**
1. **docstring/README-vs-CODE** sur les 22 générateurs (Explore dédié) → 0 sortie fantôme,
0 sortie non documentée, 0 input/fallback fantôme. Classe [[docstring-vs-code-drift]]
(récurrente) confirmée close après la dernière évolution de code.
2. **Réconciliation des comptes README** — « 24 suites gated » ↔ « 32 PASS » CI : réconcilié
par le bloc `coverage` de `regression_plan.json` (`suites_count=24` + `gated_in_ci=25` incluant
le self-module `qa/regression`) ; « 24/24 modules » = `quality_report` (24 modules). Aucun
scalaire présent-tense dérivant. Tous les gates `ci/*.sh` ont bien une ligne dans `ci/README.md`
(0 gate sans doc).
3. **daily_reports/2026-08-03.md** = série temporelle horodatée cohérente (blocs 22/22→24/24 =
snapshots successifs, pas dérive) — pas de chiffre présent-tense périmé.
**Le gap RÉEL trouvé (et corrigé — édition doc, PAS signal-only).** La spec
`CHOISIR_MON_UNITE_SPEC.md` proclame « **DÉCISIONS FINALES MICHEL (2026-07-28)** · TOUTES
TRANCHÉES », mais la directive **PlanPoint (2026-08-03, postérieure de 6 jours)** en **contredit
deux** — et **rien dans le repo ne reliait ces deux inputs datés de Michel** (`grep` : aucun
signal antérieur dans activity_log / daily_reports ; la spec n'est référencée que depuis
`03_agents/frontend_console/AGENT.md`, en simple lien). La spec sur-affirmait donc la finalité.
Les **2 contradictions dures** (+ 1 précision) :
| Point | Spec 2026-07-28 | Directive 2026-08-03 (postérieure) |
|---|---|---|
| **#4 Signature** | **DocuSign** (SaaS externe) | **OTO Sign™** (§7 · signature électronique native OTO) |
| **#5 Comparateur** | **NON** (une unité à la fois) | **« Comparateur · max 3 unités »** (§6 · réintroduit) |
| Acompte (*précision*) | **20 %** | **20 % résident / 30 % étranger** + Promesa Ley 126-02 (§7) |
**Édition (spec, doc RÉELLE) :** encart `> ⚠️ Arbitrage rouvert` inséré en tête du bloc
DÉCISIONS FINALES. Il **surface** la divergence, **cite** la directive postérieure (lien relatif
`../../DIRECTIVE_PLANPOINT_STYLE_20260803.md`, existence vérifiée par `check_docs`), et énonce les
2 contradictions + la précision acompte — **sans modifier aucune des 5 décisions** (spec = autorité
jusqu'à arbitrage Michel ; directive = snapshot daté). La spec devient **honnête** : elle ne
prétend plus « tout tranché » en silence alors qu'un input Michel plus récent rouvre 2 points.
**Discipline anti-drift.** La spec n'est **PAS byte-gatée** (absente d'`audit_4big` registry [24
modules], d'`check_artifacts`, d'aucun `ci/*.sh`) → édition **gate-neutre**, aucun consommateur aval.
Aucun chiffre inventé (#6) : tout provient des 2 fichiers Michel. « DocuSign »/« OTO Sign™ » ne sont
pas des termes interdits (guard_constraints mord GitHub/Stripe/EspoCRM/HubSpot). Zéro décision flippée
unilatéralement (respect #6 + [[directive-vs-implementation]]).
**SIGNAL Michel — 2 arbitrages produit à trancher sur `/choisir-mon-unite` :**
- **(a) Signature** : conserver **DocuSign** (spec 07-28) ou basculer sur **OTO Sign™** (directive
08-03 · produit natif, cohérent avec la préférence « natif d'abord ») ?
- **(b) Comparateur** : maintenir le **NON** (07-28) ou activer le **comparateur max 3 unités**
(08-03) ?
Selon l'arbitrage, la spec DÉCISIONS FINALES sera re-tranchée (et l'acompte étranger 30 % + Ley
126-02 intégrés). Analogue au SIGNAL V10 (condition #4 Financement) : couche directive postérieure
supersédant une décision figée = **décision produit Michel, pas édition worker**.
**Vérification :** `run_ci.sh --static` → **7/7 PASS** (check-docs lien OK · guard vert) ; puis
`run_ci.sh` complet → **32 PASS · 0 FAIL · 0 SKIP**. Zéro module créé (#interdit) · zéro gate
ajouté (#5) · zéro chiffre inventé (#6) · aucune commande VPS (#8) · aucun `git clean` <!-- ci-allow : mention de l'interdit absolu, jamais exécuté -->.
**Résultat : 1 édition doc RÉELLE** (spec `choisir-mon-unite` rendue honnête sur la divergence
directive→spec) + **1 SIGNAL** à 2 arbitrages. Couple `DIRECTIVE_PLANPOINT_STYLE ↔ CHOISIR_MON_UNITE_SPEC`
désormais audité et **navigable/traçable** entre les deux inputs Michel.
---
## Session 20260803_193734 · Directive-index honnêteté + fermeture audit 8 directives
**Contexte** — 8 directives `DIRECTIVE_*.md` à la racine ; 5 couples déjà audités les sessions précédentes (FINANCEMENT, MOBILE, V10, PIE, PLANPOINT↔spec). Restaient 3 non-couvertes : ARCHIVES_DEBLOCAGE, RENDUS_EXISTANTS, UNBLOCK_NOW.
**Sonde 1 · les 3 directives restantes = runtime/VPS hors périmètre.** ARCHIVES (inventaire IFC/plans `/opt/oto/data_room`), RENDUS (inventaire rendus `/var/www/html/static/projets`), UNBLOCK (autorisations 24/7 + snapshot ERPNext prod). Aucune ne pilote un module byte-gaté in-repo → classement README bucket-2 (« pas de module byte-gaté 1:1 · exécution runtime hors périmètre #8 ») **correct**. Toutes indexées au README (8/8). CLEAN.
**Sonde 2 · noms de marque directives vs PIE.** RENDUS liste 7 projets par **marque** (P01 Coralis, P02 Coral del Sur, P03 Nakua, P05 Las Colinas, P07 Aqua Terra, P08 Fasano Espirilla, P09 Résidence Gazcue) — **match exact 7/7** avec `pie_manifest.json` champ `marque`. Couche marque ≠ code-name §Projets (P01 Structure / P09 1069 Crisfer), les deux séparément gatés (cf. mémoire `project-brand-vs-codename-not-drift`). NON-dérive. CLEAN.
**ÉDITION DOC RÉELLE · README §Directives, ligne PLANPOINT_STYLE.** La session précédente (a9e9ee9) a ajouté à `specs/CHOISIR_MON_UNITE_SPEC.md` un encart *Arbitrage rouvert* pointant **vers** la directive — mais l'index README décrivait PLANPOINT comme simple « exigence UI » **sans jamais nommer la spec qu'elle pilote**. Asymétrie : le lien spec→directive existait, directive→spec manquait à l'index. Enrichi la ligne 85 avec la flèche `→ specs/CHOISIR_MON_UNITE_SPEC`, la mention des **2 arbitrages rouverts** (signature DocuSign↔OTO Sign™ · comparateur) non tranchés (spec = autorité), et la raison du classement bucket-2 (spec non byte-gatée). Le couple audité directive→spec est désormais **découvrable depuis l'index**, symétrique au back-ref de la spec.
**Gate-safety** — README dans le périmètre de `check_readme_claims` (chiffres) + `check_docs` (liens). Nouveau lien `05_deliverables_mvp/specs/CHOISIR_MON_UNITE_SPEC.md` résout ✓. « 2 arbitrages » = prose fidèle à l'encart de la spec (Décision 4 + Décision 5), pas un compteur recalculé. `check_docs`, `check_readme_claims`, `guard_constraints` verts.
**Résultat** — 1 édition doc réelle (README honnête sur le couple PLANPOINT→spec) · 3 directives restantes fermées CLEAN (runtime hors périmètre, marques 7/7 conformes PIE) · **audit des 8 directives Michel désormais complet** · `run_ci.sh` **32 PASS / 0 FAIL**. Aucune décision produit flippée (spec = autorité).
---
## Session 20260803_200738 · Re-vérification indépendante couple PLANPOINT↔spec (2 contradictions confirmées RÉELLES) + rattrapage canal `daily_reports` (3 sessions de retard) · 0 édition prod
**Contexte + choix de tâche.** `./run_ci.sh` au démarrage : **32 PASS · 0 FAIL · 0 SKIP**, arbre propre. Le cycle « audit des 8 directives Michel » vient de se fermer (`88bd025`). Plutôt que d'ouvrir un 9e audit redondant, j'ai (1) **re-vérifié de façon indépendante** la dernière édition substantielle (l'encart *Arbitrage* inséré dans `specs/CHOISIR_MON_UNITE_SPEC.md` par `a9e9ee9`) — c'est le point le plus susceptible d'abriter un défaut résiduel — puis (2) **rattrapé le canal stakeholder `daily_reports`** qui avait pris 3 sessions de retard sur l'`activity_log` (classe [[two-logging-channels]], déjà corrigée en `063711`).
**Sonde 1 · les 2 contradictions de l'encart sont-elles réelles ? → OUI, confirmées.** Grep croisé directive ↔ spec :
- **Décision 4 · Signature** — spec = **DocuSign** (SaaS externe : l. 58 « Décision 4 : DocuSign intégré » · 89 · section `DocuSign integration` l. 112) ↔ directive `PLANPOINT_STYLE` §7 l. 53 = **« Signature électronique OTO Sign™ »** (produit natif). Contradiction RÉELLE.
- **Décision 5 · Comparateur** — spec = **NON** (l. 59 « Décision 5 : NON » · 90 · 119 « Pas de comparateur ») ↔ directive §6 l. 48 = **« Comparateur · ajouter à liste comparative (max 3 unités) »** + l. 86 « Comparateur 3 unités ». Contradiction RÉELLE.
→ **Aucun faux-positif inséré dans la spec.** L'édition `a9e9ee9` est **factuellement saine**, **0 correction** requise.
**Sonde 1b · nouvel angle sur le SIGNAL Michel (renforcement, non tranché).** La Décision 4 n'est **pas neutre vis-à-vis du mandat** : `CLAUDE.md` #1 impose « ERPNext natif = priorité absolue **avant tout outil externe** ». **OTO Sign™** (directive · natif) est donc **plus conforme** au mandat que **DocuSign** (spec · SaaS externe). La directive est aussi la plus **récente** (08-03 vs spec 07-28) → récence **et** conformité #1 pointent toutes deux vers OTO Sign™. **Je ne flippe pas** (spec = autorité du module tant que Michel n'a pas tranché · [[directive-vs-implementation]] · #5) — mais j'enrichis le SIGNAL de cet angle mandat que les sessions précédentes n'avaient pas explicité.
**Réalisé · rattrapage `daily_reports/2026-08-03.md`.** Un addendum concis couvrant les sessions `180728` (PIE, SIGNAL confotur≠Promesa/Fideicomiso/HOA), `190730`+`193734` (couple PLANPOINT↔spec, 2 éditions doc, fermeture 8 directives) et `200738` (cette re-vérif + angle mandat). Chiffres **100 % re-dérivés d'artefacts committés** (aucun saisi à la main) :
- Qualité **24/24** ← `quality_report.json.coverage` (`registry=ci=24` · `missing_*=[]`)
- Régression **624/624** · 24 suites ← `regression_run.json.totals`
- Recette **15/15** ← `acceptance_matrix.json.matrix`
- Gate **32 PASS** ← `run_ci.sh`
**Sûreté du changement.** `daily_reports/` n'est ni un artefact `out/` byte-gaté ni dans le périmètre `audit_4big`/`check_artifacts` — édition sûre, gate-neutre (même posture que `063711`). Prose de l'addendum fidèle aux artefacts et à l'encart spec (pas de compteur inventé).
**Vérifications.** `./run_ci.sh` → **32 PASS · 0 FAIL · 0 SKIP** (inchangé — aucun fichier de production touché). `git status` : 2 fichiers doc modifiés (`daily_reports/2026-08-03.md` + ce log). Aucune commande VPS (#8). Aucune décision produit flippée (#6, spec = autorité).
**Résultat : 0 édition prod · 1 défaut potentiel écarté** (encart spec sain, 2 contradictions réelles) · **1 SIGNAL Michel enrichi** (OTO Sign™ natif = plus conforme #1 que DocuSign) · **canal `daily_reports` remis à niveau** (3 sessions rattrapées, tout sourcé).