# CI/CD · OTO Enterprise OS DTP · Gitea Actions > **Livrable Sprint 1** (roadmap `04_roadmap/ROADMAP_8_WEEKS_OR_LESS.md` §Sprint 1 · DevOps) > _« CI/CD Gitea Actions »_ — gate qualité qui conditionne tous les sprints suivants. > > **Plateforme : Gitea Actions UNIQUEMENT** (CLAUDE.md contrainte #2 · JAMAIS GitHub). --- ## 1. Ce que fait le pipeline Workflow : `.gitea/workflows/ci.yml`. Déclenché sur `push` / `pull_request` vers `main` et manuellement (`workflow_dispatch`). Jobs statiques (+ une suite `unittest` par module) : | Job | Script | Rôle | Blocant | |---|---|---|---| | `constraints-guard` | `ci/guard_constraints.sh` | Enforce les contraintes NON-NÉGOCIABLES de CLAUDE.md | ✅ oui | | `validate-json` | `ci/validate_json.sh` | Parse strict de tous les `*.json` (schémas Faisabilité↔Publiciste) | ✅ oui | | `check-docs` | `ci/check_docs.sh` | Liens Markdown internes + présence auto-score 4Big | ✅ oui (liens) | | `check-artifacts` | `ci/check_artifacts.sh` | Reproductibilité : chaque `out/*.json` versionné == build frais | ✅ oui | | `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 des docs d'entrée : chaque nombre de `README.md` « État courant », de la fiche QA « Verdict agrégé » **et** de la colonne « Tests » par suite des tables de fiches **et** des agrégats en prose (Total CRM / RBAC / e-CF) **et** des comptes par workspace des portails (cartes/liens/rôles, ×2 docs) **et** des comptes RBAC des 3 volets (table Nb rôles/profil, ligne agrégée apply_plan, ventilation userperm) **et** des comptes de mots-clés SEO (total + fr/en/es, ×2 docs) **et** du pipeline vente CRM (états/transitions, ×2 docs) **et** de l'app mobile Expo (onglets/rôles/langues/identifiants a_confirmer) **et** du DocType CONFOTUR (champs/données/sections/rôles/évènements de dépôt, ×2 docs) **et** de l'audit 5D (contrôles/dimensions ×2 docs + ventilation verdict pass/fail/à-confirmer) **et** du run-book de déploiement VPS (phases ×2 dans la fiche devops + confirmations préalables count & énumération dans le README) **et** du run-sheet de démo (nombre de modules cités dans le diagramme du README) == artefact cité (#6) | ✅ oui | | `gate` | — | Agrégat vert = gate qualité 4Big franchi | ✅ oui | Aucune dépendance réseau/marketplace hors `actions/checkout`. Tout tourne avec `bash` + `git` + `python3` (déjà présents sur un runner standard). ## 2. Détail des contrôles ### `guard_constraints.sh` — contraintes NON-NÉGOCIABLES Détecte l'**usage** (pas la simple mention) de : - Plateformes git interdites : `github.com`, `gitlab.com`, `bitbucket.org` (#2). - CRM interdits : `EspoCRM`, `HubSpot` (#3). - Paiement interdit : `Stripe` (#10 · Cardnet only). - Écriture directe dans `/var/www/html/static/` (Interdit absolu). - Commande `git clean` (Interdit absolu). - Remote git pointant ailleurs que Gitea/interne. **Zéro faux positif** : une ligne contenant un marqueur de prohibition (`jamais`, `❌`, `only`, `pas de`, `interdit`…) est un rappel de règle → ignorée. Escape hatch documenté : ajouter `ci-allow` sur une ligne pour l'exclure. ### `validate_json.sh` Parse chaque `*.json` suivi (dont `version.schema.json` et `projets_master.schema.json`, contrat de données Faisabilité → Publiciste). Un JSON cassé casse le pipeline aval → attrapé ici. ### `check_docs.sh` - **[HARD]** liens Markdown relatifs internes : la cible doit exister. - **[SOFT]** livrables `05_deliverables_mvp/*.md` : mention d'auto-score 4Big attendue (≥95/100, CLAUDE.md #5). Avertissement seul, non blocant. ### `check_artifacts.sh` Régénère chaque artefact `05_deliverables_mvp/**/out/` versionné depuis son générateur (`build -o `) et exige que tout fichier produit soit (a) **suivi par git** — jamais un artefact seulement sur disque (`out/` `.gitignore`-é ou non-`git add`), qui serait **absent en CI propre** et rendrait le gate « vert en local » par artefact fantôme (même classe que le bug `regression_run.json`) — et (b) en **égalité byte-for-byte** avec le fichier commité. Découverte automatique (zéro liste à la main · #6) : tout module avec un `out/` et un générateur `build` entre dans le gate. Cible la **dérive silencieuse** : un module auto-résout des valeurs depuis les artefacts d'**autres** modules (ex. `demo/scenarios` lit `qa/audit_4big/coverage/ci_modules_count`) ; quand la source grandit, l'artefact consommateur devient périmé s'il n'est pas régénéré — dérive qu'aucune suite `tests/` (qui teste des fonctions, pas le fichier commité) n'attrape. Correctif : `build -o out` puis commit. Les artefacts d'**exécution** non produits par `build` (p.ex. `qa/regression/out/regression_run.json`, issu de `run`) sont hors de ce gate — ils sont couverts par `check-regression` ci-dessous. ### `check_regression.sh` Rejoue la **matrice de régression** complète (`qa/regression/regression_gen.py run`, ~5 s, stdlib pur) vers un tmp et exige que le `regression_run.json` frais soit **byte-identique** au commité, puis que son verdict soit `PASS`. Complément direct de `check_artifacts` : celui-ci ne rejoue que `build` (→ `regression_plan.json`) et laisse hors périmètre l'artefact d'**exécution** `regression_run.json` — pourtant c'est *lui* qui porte le compte agrégé (suites · tests · passés · verdict) cité dans la doc et les logs — recompté par `check_readme_claims` dans les fiches d'entrée, jamais figé en dur ici. Sans ce gate, ce compte pouvait se périmer en silence (module + job CI ajoutés sans régénérer la matrice → compte de suites faux ; c'est la dérive « demo 18→21 » corrigée à la main), ou une matrice rouge être commitée verte. `regression_run.json` ne contient aucun horodatage/hôte → le run est déterministe et l'égalité exacte licite. Correctif : `regression_gen.py run -o out` 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 : - **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 **oublié** du gate (MISSING → ne bloque pas), une **référence fantôme** (DANGLING → typo / job renommé-supprimé) et un job **manuel** glissé dans `needs` (gate en attente perpétuelle sur push). - **INV-B** — chaque script `ci/*.sh` du dépôt est **câblé** : soit lancé par un job (`run: bash ci/