Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1a44dfe1c0 | |||
| 1441693e25 | |||
| db91a6bb5b | |||
| 9fa464dc84 |
@@ -9,6 +9,21 @@
|
||||
# ============================================================================
|
||||
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:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
@@ -1,5 +1,90 @@
|
||||
# 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**
|
||||
|
||||
**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]].
|
||||
|
||||
**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",
|
||||
"weight": 20,
|
||||
"passed": true,
|
||||
"evidence": "README.md (4396 octets)"
|
||||
"evidence": "README.md (4948 octets)"
|
||||
},
|
||||
{
|
||||
"criterion": "CONTRAT",
|
||||
|
||||
+28
-1
@@ -181,7 +181,7 @@ puis commit.
|
||||
### `check_ci_integrity.sh`
|
||||
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:`
|
||||
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 }
|
||||
(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
|
||||
@@ -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
|
||||
**non câblé** (script mort) ou **décâblé**, ou une **lib morte** (sourcée par
|
||||
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`,
|
||||
`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
|
||||
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
|
||||
`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
|
||||
|
||||
@@ -148,6 +148,28 @@ while IFS= read -r script; do
|
||||
fi
|
||||
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 l’en-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
|
||||
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"
|
||||
|
||||
@@ -5864,6 +5864,99 @@ if exp is not None:
|
||||
+ (f" · absents={miss}" if miss else "")
|
||||
+ (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
|
||||
# #10 — même classe d'ANCRAGE que la CITATION #10 des fiches ci-dessus, mais sur
|
||||
|
||||
@@ -73,6 +73,13 @@ cd "$ROOT" || exit 3
|
||||
CI_YML=".gitea/workflows/ci.yml"
|
||||
[ -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"
|
||||
case "${1:-}" in
|
||||
--static) MODE="static" ;;
|
||||
|
||||
Reference in New Issue
Block a user