Compare commits

..

4 Commits

Author SHA1 Message Date
Claude Code DTP Worker 1a44dfe1c0 [DTP-Worker 20260805_181325] Auto exec · session 20260805_181325
CI / Contraintes NON-NÉGOCIABLES (CLAUDE.md) (push) Has been cancelled
CI / Validation JSON (schémas Faisabilité) (push) Has been cancelled
CI / Qualité documentaire (liens + 4Big) (push) Has been cancelled
CI / Reproductibilité des artefacts out/ (build == commité) (push) Has been cancelled
CI / Fraîcheur matrice de régression (run == commité) (push) Has been cancelled
CI / Intégrité du câblage CI (gate agrège tout · gates statiques verrouillés) (push) Has been cancelled
CI / Intégrité des chiffres du README (valeur == artefact cité · (push) Has been cancelled
CI / Intégrité mobile-build.yml (gating portable · activation différée · (push) Has been cancelled
CI / Publiciste · parser + schéma + generator (unittest) (push) Has been cancelled
CI / RBAC · 50 rôles + schéma (unittest) (push) Has been cancelled
CI / Faisabilité · générateur 4 volets + round-trip (unittest) (push) Has been cancelled
CI / RBAC · fixtures ERPNext (Role + Custom DocPerm) (push) Has been cancelled
CI / RBAC · plan User Permission (row-level) (push) Has been cancelled
CI / RBAC · Role Profile (bundles par portail) (push) Has been cancelled
CI / RBAC · run-book d'application unifié (agrégat 3 volets) (push) Has been cancelled
CI / Faisabilité · dossier bancable trilingue FR/EN/ES (push) Has been cancelled
CI / CRM · workflow vente ERPNext (lead → CONFOTUR) (push) Has been cancelled
CI / CRM · DocType porteur OTO Dossier Vente (push) Has been cancelled
CI / CRM · barème commissions vendeurs (push) Has been cancelled
CI / CRM · Financement Bancaire (gate hypothécaire RD) (push) Has been cancelled
CI / Fiscal · e-CF DGII (Compupar) (push) Has been cancelled
CI / Frontend · Workspaces 5 portails rôle (push) Has been cancelled
CI / Legal · DocType CONFOTUR Application (push) Has been cancelled
CI / QA · Audit 5D conformité (push) Has been cancelled
CI / SEO · mots-clés trilingues + schema.org + hreflang (push) Has been cancelled
CI / Chat OTOIA · montage par portail (Custom Block) (push) Has been cancelled
CI / QA · Audit 4Big (95+/100 sur 100% deliverables) (push) Has been cancelled
CI / Démo · Scénarios (run-sheet P07 banquier / P05 client) (push) Has been cancelled
CI / QA · Matrice de régression exhaustive (Sprint 8) (push) Has been cancelled
CI / DevOps · Run-book de déploiement VPS unifié (Sprint 8) (push) Has been cancelled
CI / QA · Matrice d'acceptation / traçabilité MVP (Sprint 8) (push) Has been cancelled
CI / Mobile · config app Expo/EAS (navigation par rôle) (push) Has been cancelled
CI / PIE · manifest de dépendances (Annexe 12 · V10.1) (push) Has been cancelled
CI / E2E baseline Playwright (manuel) (push) Has been cancelled
CI / Gate qualité (agrégat) (push) Has been cancelled
2026-08-05 18:28:26 +00:00
Claude Code DTP Worker 1441693e25 [DTP-Worker 20260805_174324] GATE NEUF classe claude-md-constant-anchor : prose #9 des modules bancable + commissions ancrée sur CLAUDE.md. Mutation #9 (8.5%→9%) mordait DÉJÀ les 8 surfaces generator/template mais 4 copies restaient périmées EN SILENCE dans DEUX modules distincts, chacune se réclamant « (#9) »/« CLAUDE.md #9/#10 » (corriger le generator ne les touche pas → invention #6 latente) : (a) bancable/README.md + (b) banclib/finance.py docstring « 3% édition/8.5% marketing (#9) » · (c) banclib/deps.py « CLAUDE.md #9/#10 (3%/8.5%/52%·USD+DOP·Cardnet·Letter US) » 6 marqueurs · (d) crm/commissions/README.md « pourcentages canoniques (3% édition·8.5% marketing·52% équilibre) ». Extension du bloc d'ancrage existant (check_readme_claims.sh, après Template canonique) RÉUTILISANT exp (zéro re-parse). Teeth mutation-vérifiés : 8.5%→9% mord les 4 · 52%→55% mord (c)+(d) SEULS (a/b ne citent qu'édition+marketing = ciblage précis) · #10 Cardnet→Azul mord l'énum (c) ; arbre restauré = vert. Doc ci/README §2. 0 prod, 0 artefact rebuild (aucun out/ ni doc scoré audit_4big), 0 chiffre saisi (#6, recomputés de CLAUDE.md), 0 VPS (#8). run_ci 33 PASS·0 FAIL·0 SKIP.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-05 17:53:52 +00:00
Claude Code DTP Worker db91a6bb5b [DTP-Worker 20260805_171321] SWEEP docstring-vs-code étendu aux helpers *lib/ (surface qu'un scope *_gen.py rate) : Explore fan-out scopé seolib/publiciste-lib/deploylib/dvlib/finlib/wflib/commlib/ecflib/genlib, directions A+B → 0 dérive réelle. Faux-positif neuf réfuté : finlib/gate.py:58 « taux (20% résident RD, 30% étranger) » n'est PAS un hardcode — illustration des valeurs COURANTES du spec (vérifiées =financement_spec.json resident_rd=20/etranger=30), reflète la l.8 de la même docstring, cadre spec-driven posé l.15-18 ; code lit cfg.taux_apport_pct. LAISSÉ (#5). Classe RÉCURRENTE docstring-vs-code, nouvelle sous-surface (helpers) close CLEAN ce jour. 0 prod, 0 artefact, 0 gate (#5), 0 chiffre saisi (#6), 0 VPS (#8). run_ci 33 PASS·0 FAIL·0 SKIP.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-05 17:19:41 +00:00
Claude Code DTP Worker 9fa464dc84 [DTP-Worker 20260805_164319] FIX régression check-artifacts RED : quality_report.json périmé après édition doc regression/README (+11 l, 4396→4948 o) non suivie d'un rebuild audit_4big. L'audit 4Big score le contenu des docs → champ evidence byte-count dérivé. Rebuild → 1 ligne modifiée, verdict inchangé PASS 24/24 min100 coverage.ok. Classe connue rebuild-after-doc-edits ; tree HEAD 9d30939 commité RED (32 PASS·1 FAIL) rattrapé → 33 PASS·0 FAIL·0 SKIP. 0 prod, 0 gate (#5, dérive déjà gatée), 0 chiffre saisi (#6), 0 VPS (#8). 2026-08-05 16:45:50 +00:00
7 changed files with 333 additions and 2 deletions
+15
View File
@@ -9,6 +9,21 @@
# ============================================================================ # ============================================================================
name: CI name: CI
# Mode UTF-8 forcé pour TOUS les jobs (builds + suites + gates) — indépendance
# LOCALE du runner. Chaque générateur clôt son `build` par un print(✅ / accents
# français) vers stdout, dont l'encodage suit la locale. Sur un runner à locale
# ASCII (image minimale : LC_ALL=C sans coercition PEP 538, PYTHONUTF8 non posé),
# ce print lève UnicodeEncodeError APRÈS écriture des fichiers → `check-artifacts`
# voit un exit non-nul (« build a échoué ») et les 25 suites plantent : tout le
# gate passe ROUGE en silence le jour où l'image runner change, alors qu'il est
# VERT sur le poste dev (UTF-8) — classe « le runner n'est pas ton poste ».
# PYTHONUTF8=1 (PEP 540) impose stdout/stdin/défaut-fichier en UTF-8 partout,
# indépendamment de LANG/LC_ALL → tous les jobs deviennent locale-robustes.
# Verrouillé par ci/check_ci_integrity.sh (INV-C) pour ne pas disparaître en
# silence. Miroir local : run_ci.sh exporte la même variable.
env:
PYTHONUTF8: "1"
on: on:
push: push:
branches: [main] branches: [main]
+167
View File
@@ -1,5 +1,90 @@
# Activity Log · 2026-08-05 · Claude Code DTP Worker # Activity Log · 2026-08-05 · Claude Code DTP Worker
## Session 181325 · FIX RÉEL défaut locale-runner : le gate est ROUGE en silence sous locale ASCII → **VERROUILLÉ UTF-8**
**Choix de tâche.** `./run_ci.sh` au démarrage = **33 PASS**, arbre propre, roadmap
`ROADMAP_8_WEEKS_OR_LESS.md` **intégralement livrée/gatée in-repo** : les 15 promesses de
l'`acceptance_matrix` sont **toutes `status=in_repo`**, chaque reste (BIM runtime, builds EAS,
dépôts ONAPI, déploiement prod) explicitement `out_of_scope` VPS (#8). Un fan-out Explore
docstring-vs-code / prose-vs-numérique sur les livrables → **0 dérive** (repo propre). J'ai donc
attaqué un **axe de robustesse absent de la mémoire** et, contrairement aux 3 sweeps déterministes
précédents (hash-seed / locale-TZ / forward-compat, tous **CLEAN → attestation**), celui-ci a
révélé un **défaut RÉEL présent**.
**La faille — la coercition PEP 538 masquait le vrai cas.** Le sweep locale-TZ (`154314`) a rejoué
les builds sous `LANG=C`, mais Python **coerce silencieusement `C` → `C.UTF-8`** (PEP 538) : sous
`LANG=C` seul, `getpreferredencoding()` = **UTF-8** → le mode d'échec ASCII n'a **jamais été
exercé**. En neutralisant la coercition (`PYTHONUTF8=0 PYTHONCOERCECLOCALE=0 LC_ALL=C`),
l'encodage par défaut redevient **`ANSI_X3.4-1968` (ASCII)** — la config d'un **runner minimal**.
**Constat — 20 des 21 générateurs `build` PLANTENT.** Réplique de la boucle `check_artifacts` sous
locale ASCII neutralisée : **20/21 builds crashent** (`UnicodeEncodeError`), les 25 suites
échouent aussi. Cause : chaque générateur clôt `cmd_build` par un `print("✅ Bundle … généré" /
accents)` vers **stdout**, dont l'encodage suit la locale. Les fichiers sont écrits sans souci
(`encoding='utf-8'` explicite partout : 37 writes annotés, 0 write nu), mais le **print cosmétique
final** lève `UnicodeEncodeError` **après** écriture → exit non-nul → `check-artifacts` voit
« build a échoué » **ROUGE**. Vert aujourd'hui (runner UTF-8), **rouge en silence** le jour où
l'image runner change = classe « le runner n'est pas ton poste », mais **défaut, pas attestation**.
**Correctif — single-source à la frontière d'invocation, 0 fichier de production.** `PYTHONUTF8=1`
(PEP 540) impose stdout/stdin/défaut-fichier en UTF-8 **quelle que soit la locale**. Posé **une
fois** en `env:` **au niveau workflow** de `ci.yml` → s'applique à **tous les jobs** (21 builds +
25 suites + gates statiques). Miroir local : `run_ci.sh` `export PYTHONUTF8=1`. **Aucun des 20
modules touché** (anti-churn #5 — la fragilité est un contrat d'environnement d'invocation, pas 20
bugs à patcher un par un ; le fix vit là où le runner est configuré, comme le gate `cd`-ait déjà
dans le module pour fixer le contrat de CWD).
**Verrou — l'absence du fix est INVISIBLE sur runner UTF-8** (retirer l'`env:` reste vert
aujourd'hui) : classe de « vert trompeur » exactement traquée par ce dépôt. J'ancre donc le fix dans
le gate **existant** `check_ci_integrity.sh` (**INV-C**, pas de nouveau fichier gate #5) : il lit
l'`env:` top-level de `ci.yml` et exige `PYTHONUTF8: "1"`. **Teeth mutation-vérifié** : retrait de
la ligne `env:` → INV-C **mord** (exit 1) ; restauré → vert. INV-A/INV-B intacts (l'`env:` est
avant `jobs:` → hors du parseur de jobs).
**Preuve end-to-end.** `run_ci.sh` sous `env -u PYTHONUTF8 PYTHONCOERCECLOCALE=0 LC_ALL=C` (config
qui était **totalement ROUGE** avant) = **33 PASS · 0 FAIL** grâce au `export` miroir. Build-loop
sous `PYTHONUTF8=1 + LC_ALL=C` = **21 artefacts byte-identiques** (0 dérive, le fix ne change aucun
octet de sortie). `run_ci.sh` normal = **33 PASS**.
**Portée / anti-churn.** 4 fichiers : `ci.yml` (env), `run_ci.sh` (miroir), `check_ci_integrity.sh`
(INV-C verrou), `ci/README.md` (§2 détail INV-C + « Deux → Trois invariants »). **0 fichier de
production**, **0 artefact rebuild** (aucun `out/` touché → pas de rebuild `quality_report`, cf.
[[audit4big-rebuild-after-doc-edits]]), **0 chiffre inventé** (#6 — 20/21 dérivés de la boucle),
**0 commande VPS** (#8). run_ci = **33 PASS · 0 FAIL · 0 SKIP**. Mémoire neuve
*utf8-locale-runner-fix* (sœur de [[locale-tz-determinism-sweep]] dont elle **corrige l'angle mort**).
---
## Session 164319 · FIX régression RED : `quality_report.json` périmé après édition de doc → **VERT**
**Constat au démarrage.** `./run_ci.sh` = **32 PASS · 1 FAIL**`check-artifacts` RED sur
`qa/audit_4big/quality_report.json` (« DÉRIVE : le fichier commité diffère du build frais »).
Le tree HEAD `9d30939` (« Auto exec · session 161319 ») était commité **RED**. Cause racine :
cette session a grossi `05_deliverables_mvp/qa/regression/README.md` (+11 lignes, 4396→4948 octets)
mais **n'a pas rejoué** `audit_4big build`. Or l'audit 4Big **score le contenu des docs** et
consigne la taille de chaque README dans son champ `evidence` → drift mécanique, exactement la
classe [[audit4big-rebuild-after-doc-edits]] (« rebuild quality_report LAST, après toute édition de
prose »).
**Fix.** `python3 audit_4big_gen.py build -o out` → seul champ modifié :
`evidence: "README.md (4396 octets)"``"(4948 octets)"`. **Verdict inchangé PASS · 24/24 modules
≥ 95 (min 100)**, `coverage.ok=true` : la régression était purement l'echo d'une taille de fichier,
pas une perte de qualité. `MANIFEST.json` non touché (cohérent → `check_artifacts` revenu VERT).
**Recompute indépendant (aucun chiffre saisi · #6).** Diff commité↔build frais = **1 ligne** (le
byte-count). `run_ci.sh` post-fix = **33 PASS · 0 FAIL · 0 SKIP** (8 statiques + 25 suites).
**Portée / anti-churn.** **0 fichier de production édité** (seul l'artefact régénéré), **0 gate
ajouté** (#5 — la dérive était **déjà détectée** par `check_artifacts`, c'est la discipline
« rebuild LAST » qui a manqué, pas une gate), **0 chiffre saisi** (#6 — 4948 dérivé de la taille
réelle du README par le générateur), **aucune commande VPS** (#8). Classe connue en mémoire ;
attestation que le gate `check_artifacts` **mord sur le tree commité** (RED opposable, pas seulement
pré-commit) — jumelle de l'incident `144304`.
**Vérif.** `git diff --stat` = 1 fichier / 1 ligne · `./run_ci.sh`**33 PASS · 0 FAIL · 0 SKIP**.
---
## Session 154314 · SWEEP neuf : indépendance LOCALE / TIMEZONE des artefacts `build` → **CLEAN** ## Session 154314 · SWEEP neuf : indépendance LOCALE / TIMEZONE des artefacts `build` → **CLEAN**
**Contexte + choix de tâche.** `./run_ci.sh` au démarrage : **33 PASS · 0 FAIL · 0 SKIP**, arbre **Contexte + choix de tâche.** `./run_ci.sh` au démarrage : **33 PASS · 0 FAIL · 0 SKIP**, arbre
@@ -1452,3 +1537,85 @@ sur l'arbre édité : `check_docs` ✅ (liens internes OK), `guard_constraints`
daily_report `2026-08-05.md` (section `144304`) + ce journal. Classe [[two-logging-channels]]. daily_report `2026-08-05.md` (section `144304`) + ce journal. Classe [[two-logging-channels]].
**Vérif.** `./run_ci.sh` → **33 PASS · 0 FAIL · 0 SKIP** (aucun fichier gaté touché). **Vérif.** `./run_ci.sh` → **33 PASS · 0 FAIL · 0 SKIP** (aucun fichier gaté touché).
---
## Session `20260805_171321` · Sweep docstring-vs-code étendu aux helpers `*lib/` — CLEAN, 1 faux-positif neuf réfuté
**Constat.** `run_ci.sh` → **33 PASS · 0 FAIL · 0 SKIP**. Roadmap fonctionnelle
intégralement livrée+gatée ; restes hors périmètre worker (VPS/EAS/ONAPI · #8). Aucune
tâche fonctionnelle in-repo résiduelle. Valeur honnête du jour : étendre la veille
d'intégrité `docstring-vs-code` (classe RÉCURRENTE/non-close, cf. [[docstring-vs-code-drift]])
à la **surface qu'un balayage scopé `*_gen.py` rate** : les paquets d'aide `*lib/`.
**Sweep exécuté (lecture-seule).** Fan-out `Explore` scopé UNIQUEMENT aux helpers
`seolib` · `publiciste/lib` · `deploylib` · `dvlib` · `finlib` · `wflib` · `commlib` ·
`ecflib` · `genlib` — docstrings module + fonction, directions A (annonce non-implémentée)
ET B (omet une sortie réelle) → **0 dérive réelle**.
**Faux-positif neuf réfuté (vérifié en source + contre le spec).** Le seul candidat
remonté : `crm/financement_bancaire/finlib/gate.py:58`, docstring de `apport_requis_usd`
« Apport requis = prix × taux (**20 % résident RD, 30 % étranger**) », soupçonnée de
« coder en dur une constante que le code lit du spec ». **NON — accurate-to-contract** :
la parenthèse **illustre les valeurs COURANTES du spec**, vérifiées égales à
`financement_spec.json` (`resident_rd`=20 · `etranger`=30) ; elle reflète la ligne 8 de la
MÊME docstring module (jugée correcte) ; les lignes 15-18 posent déjà le cadre « JAMAIS
codés en dur, viennent du contrat ». Le code (l.59-64) lit bien `cfg.taux_apport_pct[...]`.
→ « docstring citant la valeur courante d'un nombre spec-driven en illustration » =
même famille que l'exclusion pédagogique/illustrative. **LAISSÉ** (anti-churn #5) — pas
d'harmonisation, pas de re-flag. Leçon consignée en mémoire : vérifier la valeur COURANTE
du spec avant de qualifier de contradiction-hardcode.
**Portée / anti-churn.** **0 fichier de production édité**, **0 artefact reconstruit**
(aucun `out/*.json` touché → pas de rebuild `quality_report`, cf.
[[audit4big-rebuild-after-doc-edits]]), **0 gate ajouté** (#5 — les docstrings sont de la
prose, faible valeur à gater ; la parenthèse est de surcroît exacte), **0 chiffre saisi à
la main** (#6 — les 20/30 sont lus du spec, non retapés), **aucune commande VPS** (#8 —
100 % local). Éditions : mémoire `docstring-vs-code-drift.md` (note du faux-positif) + ce
journal.
**Vérif.** `./run_ci.sh` → **33 PASS · 0 FAIL · 0 SKIP** (aucun fichier gaté touché).
---
## Session 20260805_174324 · GATE NEUF — prose #9 des modules bancable + commissions ancrée sur CLAUDE.md
**Contexte.** Repo vert au démarrage (33 PASS · 0 FAIL · 0 SKIP), tous modules livrés,
roadmap 8-sem sans tâche-feature ouverte. Sonde ciblée sur la classe **claude-md-constant-anchor-gate** :
les constantes canoniques #9 (3 % édition · 8.5 % marketing · 52 % point d'équilibre)
recopiées hors du module generator.
**Constat (mutation-prouvé).** `sed` #9 `8.5 %→9 %` sur `CLAUDE.md` → le gate
`check_readme_claims.sh` fait rougir **8 surfaces generator/template** (model.py CANONICAL,
README, renderer `_CANONICAL_MARKERS`, oracle test, 2 docstrings scorer, template intro/§5.2/§7)
— l'ancre est vivante. **MAIS 4 copies restaient périmées EN SILENCE**, dans DEUX modules
DISTINCTS, chacune se réclamant explicitement « (#9) » / « CLAUDE.md #9/#10 » :
- (a) `faisabilite/bancable/README.md` — « 3 % édition » et « 8.5 % marketing » (#9)
- (b) `faisabilite/bancable/banclib/finance.py` — MÊME phrase, en docstring
- (c) `faisabilite/bancable/banclib/deps.py` — « CLAUDE.md #9/#10 (3 %/8.5 %/52 % · USD+DOP · Cardnet · Letter US) » (6 marqueurs)
- (d) `crm/commissions/README.md` — « pourcentages canoniques (3 % édition · 8.5 % marketing · 52 % point d'équilibre) »
Corriger le generator ne TOUCHE pas ces modules → invention #6 latente, de la classe déjà
fermée 10× (publiciste #10 · fiscal moneda · fiches #10 · template canonique).
**Fix.** Extension du bloc d'ancrage existant dans `ci/check_readme_claims.sh` (juste après
« Template canonique »), **réutilisant `exp`** déjà recomputé des lignes #9/#10 de `CLAUDE.md`
(zéro re-parse, zéro duplication). Un claim absent échoue AUSSI (traçabilité #6). Documenté
en `ci/README.md` §2 (paragraphe « paramètres canoniques »), pas de nouvelle ligne au
tableau §1 (cf. [[ci-readme-table-detail-in-section2]]).
**Teeth (mutation-vérifié, arbre restauré à chaque fois).**
- `#9 8.5 %→9 %` → mord les **4** surfaces.
- `#9 52 %→55 %` → mord **(c)+(d) SEULEMENT** ; (a)/(b) ne citent qu'édition+marketing ⇒
correctement épargnées = **ciblage précis** (pas de faux-rouge).
- `#10 Cardnet→Azul` → mord l'énumération 6-marqueurs de (c).
- Arbre restauré → gate exit=0, les 4 surfaces re-vertes.
**Portée / anti-churn.** **0 fichier de production édité** (seuls `ci/check_readme_claims.sh`
+ `ci/README.md`), **0 artefact reconstruit** (aucun `out/*.json` ni module doc scoré par
audit_4big → pas de rebuild `quality_report`), **0 chiffre saisi à la main** (#6 — les 6
valeurs sont recomputées de `CLAUDE.md`, jamais retapées), **aucune commande VPS** (#8 —
100 % local). Classe RÉCURRENTE (chaque module distinct qui recopie une constante #9/#10
citée est une surface à ancrer) : nouvelle sous-surface (bancable + commissions) close.
**Vérif.** `./run_ci.sh` → **33 PASS · 0 FAIL · 0 SKIP**.
@@ -984,7 +984,7 @@
"criterion": "DOC", "criterion": "DOC",
"weight": 20, "weight": 20,
"passed": true, "passed": true,
"evidence": "README.md (4396 octets)" "evidence": "README.md (4948 octets)"
}, },
{ {
"criterion": "CONTRAT", "criterion": "CONTRAT",
+28 -1
View File
@@ -181,7 +181,7 @@ puis commit.
### `check_ci_integrity.sh` ### `check_ci_integrity.sh`
Prouve, en lisant `.gitea/workflows/ci.yml`, que le **câblage** du workflow tient — Prouve, en lisant `.gitea/workflows/ci.yml`, que le **câblage** du workflow tient —
car le job `gate` est le **seul verrou de merge** : un check absent de son `needs:` car le job `gate` est le **seul verrou de merge** : un check absent de son `needs:`
ne bloque **rien**, même rouge. Deux invariants : ne bloque **rien**, même rouge. Trois invariants :
- **INV-A** — `gate.needs` == { tous les jobs définis } { `gate` } { jobs manuels } - **INV-A** — `gate.needs` == { tous les jobs définis } { `gate` } { jobs manuels }
(un job manuel = gardé par `if: … workflow_dispatch …`, ex. `e2e-baseline`, (un job manuel = gardé par `if: … workflow_dispatch …`, ex. `e2e-baseline`,
légitimement hors du gate push/PR car il exige un serveur live). Détecte un job légitimement hors du gate push/PR car il exige un serveur live). Détecte un job
@@ -193,6 +193,21 @@ ne bloque **rien**, même rouge. Deux invariants :
script (lib partagée `ci/lib.sh` — jamais un job propre). Un nouveau gate statique script (lib partagée `ci/lib.sh` — jamais un job propre). Un nouveau gate statique
**non câblé** (script mort) ou **décâblé**, ou une **lib morte** (sourcée par **non câblé** (script mort) ou **décâblé**, ou une **lib morte** (sourcée par
personne), casse le check. personne), casse le check.
- **INV-C** — le workflow force le **mode UTF-8 de Python** (`env: PYTHONUTF8: "1"`
au niveau workflow → tous les jobs). Motif : chaque générateur clôt son `build`
par un `print("✅ …" / accents français)` vers stdout, dont l'encodage suit la
**locale du runner**. Sur une image runner ASCII (`LC_ALL=C` sans coercition
PEP 538, `PYTHONUTF8` non posé), ce print lève `UnicodeEncodeError` **après**
écriture des fichiers → `check-artifacts` voit un exit non-nul et les 25 suites
plantent : **tout le gate passe ROUGE le jour où l'image runner change**, alors
qu'il est VERT sur le poste dev (UTF-8) — classe « le runner n'est pas ton poste »
(sœur des sweeps hash-seed / locale-TZ / forward-compat, mais ici un **défaut
réel présent**, pas une simple attestation). `PYTHONUTF8=1` (PEP 540) impose
stdout/stdin/défaut-fichier en UTF-8 **partout** → 20 builds qui crashaient +
les suites redeviennent locale-robustes (prouvé : `run_ci.sh` sous
`LC_ALL=C` neutralisé = 33 PASS). Comme l'absence de cet `env:` est **invisible**
sur un runner UTF-8, INV-C l'**ancre** : le retirer casse ce gate au lieu de
dériver en silence. `run_ci.sh` **exporte la même variable** (miroir fidèle).
Ferme le trou laissé par les couvertures existantes (`audit_4big/registry`, Ferme le trou laissé par les couvertures existantes (`audit_4big/registry`,
`qa/regression/discovery`) qui ne prouvent l'appartenance au gate que des jobs de `qa/regression/discovery`) qui ne prouvent l'appartenance au gate que des jobs de
@@ -1196,6 +1211,18 @@ vérifiées : renderer `Cardnet→Azul` (absent/en-trop) · test oracle retire `
mord SIMULTANÉMENT model.py + renderer + test + les DEUX docstrings** (la valeur mord SIMULTANÉMENT model.py + renderer + test + les DEUX docstrings** (la valeur
même de l'ancrage) ; restauré = vert. même de l'ancrage) ; restauré = vert.
Même classe enfin sur QUATRE copies **hors du module generator** — la prose #9 de
DEUX modules distincts qui se réclame explicitement « (#9) » / « CLAUDE.md #9/#10 »
mais qu'aucun gate ne liait à la contrainte (corriger le generator ne les touche pas) :
(a) `faisabilite/bancable/README.md` et (b) sa docstring `banclib/finance.py`
(« 3 % édition » et « 8.5 % marketing » (#9)), (c) `banclib/deps.py`
(« CLAUDE.md #9/#10 (3 %/8.5 %/52 % · USD+DOP · Cardnet · Letter US) », 6 marqueurs),
(d) `crm/commissions/README.md` (« pourcentages canoniques (3 % édition · 8.5 %
marketing · 52 % point d'équilibre) »). On RÉUTILISE `exp` (zéro re-parse). Morsures
vérifiées : `#9 8.5 %→9 %` mord les 4 · `#9 52 %→55 %` mord (c)+(d) SEULS ((a)/(b) ne
citent qu'édition+marketing ⇒ correctement épargnées, ciblage précis) · `#10
Cardnet→Azul` mord l'énumération (c). Un claim absent échoue AUSSI (traçabilité #6).
Même classe enfin sur la **fiche d'identité du rôle RBAC mobile** — la fiche agent Même classe enfin sur la **fiche d'identité du rôle RBAC mobile** — la fiche agent
`03_agents/mobile/AGENT.md`. Les builds/soumissions stores étant **hors-repo** (#8), `03_agents/mobile/AGENT.md`. Les builds/soumissions stores étant **hors-repo** (#8),
l'agent mobile le reconnaît et déclare que son **seul ancrage in-repo vérifiable** est l'agent mobile le reconnaît et déclare que son **seul ancrage in-repo vérifiable** est
+22
View File
@@ -148,6 +148,28 @@ while IFS= read -r script; do
fi fi
done < <(git ls-files 'ci/*.sh' | grep -vx 'ci/check_ci_integrity.sh') done < <(git ls-files 'ci/*.sh' | grep -vx 'ci/check_ci_integrity.sh')
# --- INV-C : le workflow force le mode UTF-8 de Python (indépendance locale) --
# Chaque générateur clôt son `build` par un print(✅/accents) vers stdout ; sous
# une locale ASCII (runner minimal : LC_ALL=C, sans coercition PEP 538), ce print
# lève UnicodeEncodeError → `check-artifacts` ROUGE et les 25 suites plantent,
# EN SILENCE le jour où l'image runner change (VERT aujourd'hui car UTF-8). Le
# correctif est un `env: PYTHONUTF8: "1"` au niveau WORKFLOW (s'applique à tous
# les jobs). Comme son absence est invisible sur un runner UTF-8, on l'ancre ici :
# le retirer casse ce gate au lieu de dériver silencieusement. On lit l'`env:`
# top-level (indenté 2 espaces, AVANT `jobs:` → hors du parseur de jobs).
echo "== INV-C · le workflow force PYTHONUTF8 (I/O UTF-8 · indépendance locale runner) =="
utf8_mode="$(awk '
/^jobs:[[:space:]]*$/ { exit } # ne considérer que len-tête (avant jobs:)
/^env:[[:space:]]*$/ { inenv=1; next }
inenv && /^[^[:space:]]/ { inenv=0 } # fin du bloc env: (clé non indentée)
inenv && match($0, /^[[:space:]]+PYTHONUTF8:[[:space:]]*["'\'']?1["'\'']?[[:space:]]*$/) { print "1" }
' "$CI")"
if [[ "$utf8_mode" == "1" ]]; then
ok "workflow env: PYTHONUTF8=\"1\" → stdout/défaut-fichier UTF-8 sur tout runner (builds + suites locale-robustes)"
else
report "workflow SANS \`env: PYTHONUTF8: \"1\"\` top-level → gate locale-fragile (RED silencieux sur runner ASCII · cf. print ✅ en fin de build)"
fi
echo echo
if [[ "$FAIL" -eq 0 ]]; then if [[ "$FAIL" -eq 0 ]]; then
echo -e "\033[32m✅ Câblage CI intègre (gate agrège tout job non-manuel · gates statiques verrouillés).\033[0m" echo -e "\033[32m✅ Câblage CI intègre (gate agrège tout job non-manuel · gates statiques verrouillés).\033[0m"
+93
View File
@@ -5864,6 +5864,99 @@ if exp is not None:
+ (f" · absents={miss}" if miss else "") + (f" · absents={miss}" if miss else "")
+ (f" · en trop={extra}" if extra else "") + " → régénérer le template") + (f" · en trop={extra}" if extra else "") + " → régénérer le template")
# ============================================================================
# Bancable + Commissions · PROSE #9 (« pourcentages canoniques ») ANCRÉE sur
# CLAUDE.md — même classe d'ANCRAGE que « paramètres canoniques » (generator) et
# « Template canonique » ci-dessus, mais sur des MODULES DISTINCTS
# (faisabilite/bancable, crm/commissions) dont la doc RECOPIE à la main les
# pourcentages #9 en se réclamant EXPLICITEMENT « (#9) » / « CLAUDE.md #9/#10 »,
# sans qu'aucun gate ne les liât à la contrainte.
# ----------------------------------------------------------------------------
# Le bloc « paramètres canoniques » ancre le SEUL module generator (model.py /
# README / renderer / scorer / test) + le TEMPLATE. Restent QUATRE copies
# data-derived dans DEUX autres modules, toutes invisibles à check_artifacts
# (prose/docstrings hors out/) et non attrapées par leurs tests/ (FONCTIONS) :
# (a) faisabilite/bancable/README.md — « 3 % édition » et « 8.5 % marketing » (#9)
# (b) faisabilite/bancable/banclib/finance.py — MÊME phrase, en docstring
# (c) faisabilite/bancable/banclib/deps.py — « CLAUDE.md #9/#10 (3 %/8.5 %/52 %·…) »
# (d) crm/commissions/README.md — « (3 % édition · 8.5 % marketing · 52 % …) »
# La mutation #9 (ex. 8.5 %→9 %) fait DÉJÀ rougir les 8 surfaces generator/template,
# MAIS ces 4 copies-là restent périmées EN SILENCE : corriger le generator ne les
# TOUCHE pas (modules distincts) — invention #6 latente, de la classe déjà fermée
# 10× pour publiciste #10 / fiscal moneda / fiches #10. On RÉUTILISE `exp`
# (recompute #9 depuis CLAUDE.md · zéro duplication). Un claim absent échoue AUSSI.
# ============================================================================
if exp is not None:
_p_edt = _pnorm(exp["frais_edition_pct"]); _p_mkt = _pnorm(exp["marketing_pct"])
_p_eql = _pnorm(exp["point_equilibre_pct"])
_want6b = {_pnorm(exp[k]) for k in _num_keys} | {_ai(exp[k]) for k in _txt_keys}
# (a)+(b) même phrase « « E % édition » et « M % marketing » (#9) »
_duo_re = re.compile(r"«\s*([\d.]+)\s*%\s*[ée]dition\s*»\s*et\s*"
r"«\s*([\d.]+)\s*%\s*marketing\s*»\s*\(#9\)")
for _lbl, _rel in (("bancable/README", "faisabilite/bancable/README.md"),
("bancable/finance.py", "faisabilite/bancable/banclib/finance.py")):
_txt = read_norm(f"{D}/{_rel}")
if _txt is None:
bad(f"Canoniques prose #9 · {_lbl} illisible"); continue
_mm = _duo_re.search(_txt)
if _mm is None:
bad(f"Canoniques prose #9 · {_lbl} — phrase « « N % édition » et "
f"« N % marketing » (#9) » INTROUVABLE (traçabilité #6)")
else:
_got = {_pnorm(float(_mm.group(1))), _pnorm(float(_mm.group(2)))}
_want = {_p_edt, _p_mkt}
if _got == _want:
good(f"Canoniques prose #9 · {_lbl} — {_p_edt} édition · {_p_mkt} "
f"marketing == CLAUDE.md #9")
else:
bad(f"Canoniques prose #9 · {_lbl} — cite {sorted(_got)} MAIS CLAUDE.md "
f"{sorted(_want)} → aligner (ou CLAUDE.md)")
# (c) deps.py « CLAUDE.md #9/#10 (3 % / 8.5 % / 52 % · USD+DOP · Cardnet · Letter US) »
_dep = read_norm(f"{D}/faisabilite/bancable/banclib/deps.py")
if _dep is None:
bad("Canoniques prose #9 · bancable/deps.py illisible")
else:
_md = re.search(r"CLAUDE\.md #9/#10\s*\(([^)]+)\)", _dep)
if _md is None:
bad("Canoniques prose #9 · bancable/deps.py — « CLAUDE.md #9/#10 (…) » "
"INTROUVABLE (traçabilité #6)")
else:
_got = set()
for _tok in re.split(r"[/·]", _md.group(1)):
_p = _pct(_tok)
_got.add(_pnorm(_p) if _p is not None else _ai(_tok))
_got.discard("")
if _got == _want6b:
good("Canoniques prose #9 · bancable/deps.py — 6 marqueurs == "
"CLAUDE.md #9/#10")
else:
_miss = sorted(_want6b - _got); _extra = sorted(_got - _want6b)
bad(f"Canoniques prose #9 · bancable/deps.py — énumère {sorted(_got)} MAIS "
f"CLAUDE.md {sorted(_want6b)}"
+ (f" · absents={_miss}" if _miss else "")
+ (f" · en trop={_extra}" if _extra else "") + " → aligner (ou CLAUDE.md)")
# (d) commissions/README « pourcentages canoniques (E % édition · M % marketing · Q % …) »
_com = read_norm(f"{D}/crm/commissions/README.md")
if _com is None:
bad("Canoniques prose #9 · commissions/README illisible")
else:
_mc = re.search(r"pourcentages canoniques\s*\(([^)]*%[^)]*)\)", _com)
if _mc is None:
bad("Canoniques prose #9 · commissions/README — « pourcentages canoniques "
"(… %) » INTROUVABLE (traçabilité #6)")
else:
_got = {_pnorm(float(_x)) for _x in re.findall(r"([\d.]+)\s*%", _mc.group(1))}
_want = {_p_edt, _p_mkt, _p_eql}
if _got == _want:
good(f"Canoniques prose #9 · commissions/README — {sorted(_want)} == "
f"CLAUDE.md #9")
else:
_miss = sorted(_want - _got); _extra = sorted(_got - _want)
bad(f"Canoniques prose #9 · commissions/README — cite {sorted(_got)} MAIS "
f"CLAUDE.md {sorted(_want)}"
+ (f" · absents={_miss}" if _miss else "")
+ (f" · en trop={_extra}" if _extra else "") + " → aligner (ou CLAUDE.md)")
# ============================================================================ # ============================================================================
# Fiscal · ecf_dgii — le JEU DE DEVISES `moneda` (`USD`/`DOP`) ANCRÉ sur CLAUDE.md # Fiscal · ecf_dgii — le JEU DE DEVISES `moneda` (`USD`/`DOP`) ANCRÉ sur CLAUDE.md
# #10 — même classe d'ANCRAGE que la CITATION #10 des fiches ci-dessus, mais sur # #10 — même classe d'ANCRAGE que la CITATION #10 des fiches ci-dessus, mais sur
+7
View File
@@ -73,6 +73,13 @@ cd "$ROOT" || exit 3
CI_YML=".gitea/workflows/ci.yml" CI_YML=".gitea/workflows/ci.yml"
[ -f "$CI_YML" ] || { echo "run_ci.sh : $CI_YML introuvable." >&2; exit 3; } [ -f "$CI_YML" ] || { echo "run_ci.sh : $CI_YML introuvable." >&2; exit 3; }
# Miroir de l'`env:` workflow de ci.yml — force le mode UTF-8 de Python pour
# tous les jobs rejoués ici, de sorte que ce miroir local reproduise fidèlement
# le gate même si le poste tourne sous une locale ASCII (LC_ALL=C). Sans cela,
# les générateurs (print ✅/accents en fin de `build`) et les suites planteraient
# ici alors qu'ils passent en CI (cf. ci.yml env PYTHONUTF8 · check_ci_integrity INV-C).
export PYTHONUTF8=1
MODE="all" MODE="all"
case "${1:-}" in case "${1:-}" in
--static) MODE="static" ;; --static) MODE="static" ;;