[DTP-Worker 20260805_001109] Nouveau gate check-mobile-workflow : la structure de mobile-build.yml devient opposable au merge (surface mutation-testée UNGATED, 1re couverture) → 32→33 jobs

- ci/check_mobile_workflow.sh (8e gate statique) · 4 invariants lus dans le fichier (#6) :
  MOB-1 bien-formé (YAML strict si PyYAML) · MOB-2 aucun secrets. en if: de job
  (portabilité act/Gitea, verrouille le correctif 234104) · MOB-3 builds gatés sur
  needs.preflight.outputs.has_token (activation différée #6/#8) · MOB-4 contrat outputs
  (déclarés==référencés==alimentés, 0 dangling).
- Preuve UNGATED avant ajout : réintroduire secrets. en if: de job → 7/7 gates statiques verts.
- Gate mutation-testé : 5 mutations toutes ROUGES, fichier propre re-VERT.
- Câblé dans ci.yml + gate.needs ; check-ci-integrity INV-A/B verts ; run_ci auto → 33 PASS.
- ci/README.md : ligne §1 + détail §2 + nuance « Second workflow ».
- 0 nouveau module · 0 chiffre inventé · 0 edition prod hors CI · aucune commande VPS.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Code DTP Worker
2026-08-05 00:21:58 +00:00
parent c5c48dff53
commit b7ffb6448f
4 changed files with 311 additions and 1 deletions
+47
View File
@@ -21,6 +21,7 @@ et manuellement (`workflow_dispatch`). Jobs statiques (+ une suite `unittest` pa
| `check-regression` | `ci/check_regression.sh` | Fraîcheur : `qa/regression/out/regression_run.json` (`run`) == run frais + verdict PASS | ✅ oui |
| `check-ci-integrity` | `ci/check_ci_integrity.sh` | Intégrité du câblage : `gate.needs` == tous les jobs non-manuels · chaque `ci/*.sh` câblé au gate | ✅ oui |
| `check-readme-claims` | `ci/check_readme_claims.sh` | Intégrité des chiffres **et des faits data-derived** des docs d'entrée (`README.md` « État courant », fiches `03_agents/*/AGENT.md`, READMEs de module) : chaque nombre, label de verdict et identité citée en prose (rôle · DocType · entité · marque · persona…) est **recomputé de l'artefact commité qu'il cite** — ou re-dérivé de `CLAUDE.md` / de la roadmap pour les valeurs ancrées au mandat — puis exigé **exact** (#6), jamais une liste à la main : un doc qui se contredit avec sa propre source est un « vert trompeur ». Surfaces couvertes : comptes de tests/suites, verdicts & barème 4Big, ledgers d'invariants CI, ventilations & identités RBAC, mots-clés & graphe SEO, pipeline vente, e-CF DGII, CONFOTUR, app mobile Expo, run-books VPS, tokens design #4, etc. **Détail exhaustif surface par surface : §2 `check_readme_claims.sh` (ci-dessous).** | ✅ oui |
| `check-mobile-workflow` | `ci/check_mobile_workflow.sh` | Intégrité du **second** workflow `mobile-build.yml` (hors gate de merge, mais sa STRUCTURE est gatée ici) : gating portable (0 `secrets.` en `if:` de job → pas de SKIP silencieux) · builds gatés sur les outputs `preflight` (activation différée #6/#8) · contrat d'outputs (déclarés == référencés == alimentés). **Détail : §2 `check_mobile_workflow.sh`.** | ✅ oui |
| `gate` | — | Agrégat vert = gate qualité 4Big franchi | ✅ oui |
Aucune dépendance réseau/marketplace hors `actions/checkout`. Tout tourne avec
@@ -41,6 +42,17 @@ il ne rend **jamais** le CI rouge (`preflight` reste vert et rapporte les secret
attendus). Périmètre complet (#8 · exécution serveur hors repo), profils EAS et
variables `a_confirmer` : [`README` du module Mobile](../05_deliverables_mvp/mobile/app_config/README.md).
Nuance importante : ses **jobs** sont hors gate de merge, mais sa **structure de
fichier**, elle, EST gatée — par le job `check-mobile-workflow`
([`ci/check_mobile_workflow.sh`](check_mobile_workflow.sh), dans `gate.needs`).
Sans lui, une régression du workflow passait VERTE en silence (mutation vérifiée :
réintroduire `secrets.` dans un `if:` de job laissait les 7 gates statiques verts).
C'est précisément le risque « marche dans ma tête, casse le jour où Michel active
`EAS_TOKEN` » : le contexte `secrets` n'est pas disponible dans `jobs.<id>.if`
(table d'availability GitHub Actions), donc un tel `if:` s'évaluerait vide sur
`act`/Gitea → build **SKIP silencieux** même token fourni. Le gate rend cet
invariant (et l'activation différée) opposable au merge.
### Rejouer le gate EN LOCAL — `run_ci.sh` (racine du dépôt)
Avant de committer, reproduire l'intégralité du gate de merge en une commande :
@@ -1599,6 +1611,41 @@ Un claim **absent** échoue aussi (la dérive de formulation qui ferait
disparaître un chiffre est elle-même une régression de traçabilité). stdlib pur
(bash/git/python3), zéro réseau.
### `check_mobile_workflow.sh` — intégrité du second workflow `mobile-build.yml`
`mobile-build.yml` (EAS build iOS/Android · Sprint 5) est **hors `gate.needs`** :
aucun de ses jobs ne conditionne un merge (ils s'activent seulement quand Michel
fournit `EAS_TOKEN`). Conséquence longtemps tolérée : **aucun gate ne lintait sa
structure** — `check_ci_integrity.sh` ne verrouille que `ci.yml`, `validate_json.sh`
que le `.json`. Mutation vérifiée : réintroduire `secrets.` dans un `if:` de job y
passait **7/7 gates statiques verts**. Or ce fichier porte un correctif de
portabilité subtil (session `234104`) qui, s'il régresse, ne casse rien au CI mais
fait échouer les builds « le jour où on en a besoin ». Ce gate rend 4 invariants
opposables, tous **lus dans le fichier** (jamais une liste à la main · #6) :
- **MOB-1 · bien-formé** — le fichier existe, porte `name:`/`on:`/`jobs:` et les
3 jobs attendus (`preflight`, `build-ios`, `build-android`). Parse YAML strict
**exigé si PyYAML est présent**, sinon contrôle awk seul (runner sans pip →
best-effort ; jamais un faux vert, car MOB-2/3/4 ne dépendent pas de PyYAML).
- **MOB-2 · portabilité du gating** — **0** `secrets.` dans un `if:` au niveau
**job** (`^ if:`). Le contexte `secrets` n'est **pas** exposé dans
`jobs.<id>.if` (table d'availability GitHub Actions) ; sur `act`/Gitea il
s'évaluerait vide → job **SKIP silencieux** même token fourni. Exactement la
régression corrigée en `234104` (passage au pattern preflight-output).
- **MOB-3 · activation différée préservée (#6/#8)** — chaque job de **build**
(bloc lançant `run: eas build`) porte un `if:` job-level gaté sur
`needs.preflight.outputs.has_token` → SKIP tant que le token absent, donc le
workflow ne rend **jamais** le CI rouge avant activation Michel.
- **MOB-4 · contrat d'outputs intact** — `preflight` déclare `has_token`/`has_repo`,
chaque `needs.preflight.outputs.<X>` référencé est bien **déclaré** (0 dangling →
pas de SKIP par output fantôme) et chaque output déclaré est **alimenté** par un
`echo "<X>=… >> $GITHUB_OUTPUT"`.
Les 5 mutations correspondantes (YAML cassé · `secrets.` en `if:` de job · `if:`
de build supprimé · output fantôme référencé · output non alimenté) rendent toutes
le gate **ROUGE** ; le fichier propre repasse **vert**. stdlib + awk pur, PyYAML
best-effort, zéro réseau. Ce gate est le **8ᵉ gate statique** `ci/*.sh` (câblé dans
`gate.needs`, donc rejoué par `run_ci.sh`).
### `lib.sh` — helper partagé (sourcé, jamais exécuté seul)
Les gates commençaient tous par `cd "$(git rev-parse --show-toplevel)"`. **Hors**
d'un arbre de travail git (tarball, `git archive | tar -x`, `git` absent du PATH),