Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
16 KiB
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) == 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 <tmp>) 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é parif: … 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é dansneeds(gate en attente perpétuelle sur push). - INV-B — chaque script
ci/*.shdu dépôt est câblé : soit lancé par un job (run: bash ci/<script>) lui-même dansgate.needs, soit sourcé par ≥1 gate script (lib partagéeci/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.
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
module (ceux portant un working-directory:) — les gates statiques sans
working-directory n'étaient gardés par personne. Même classe d'anti-dérive que INV4
(disque→CI), appliquée au câblage CI. stdlib pur (bash/awk/git), zéro réseau.
check_readme_claims.sh
Le README.md est le point d'entrée du mandat ; sa section « État courant
(sourcé) » affiche des chiffres et déclare « Chaque chiffre ci-dessous est sourcé
vers un artefact commité (anti-invention #6) ; ce README n'introduit aucune donnée
nouvelle. » Cette promesse n'était gardée par aucun gate : check_docs.sh ne
valide que les liens (la cible existe), jamais la valeur des nombres. Quand un
module + son job CI sont ajoutés (21→22 modules, 21→22 suites) ou qu'une promesse
change de statut (14 in_repo + 1 out_of_scope → 15 in_repo), les chiffres du
README se périment en silence tout en restant « sourcés » vers un artefact qui dit
autre chose — un README qui se contredit avec sa propre source est un « vert
trompeur » (même classe que la matrice périmée « demo 18→21 » ou INV4, appliqué à la
doc d'entrée). Ce gate recompute chaque chiffre depuis l'artefact cité (jamais une
liste à la main · #6) et exige l'égalité avec ce qui est écrit dans les trois
docs d'entrée — README.md :
- modules gated
N/MàK/100+ verdictPASS→qa/audit_4big/out/quality_report.json(totals) ; Nsuites gated →qa/regression/out/regression_plan.json(totals.suites) ;Npromesses (Xsprint +Ymétriques),Zin_repo, verdicttrue→qa/acceptance/out/acceptance_matrix.json;- « 13 agents » (×2 : nav + titre) →
git ls-files 03_agents/*/AGENT.md.
Et la fiche QA 03_agents/qa/AGENT.md (section « Verdict agrégé courant », qui se
disait « jamais compté à la main » tout en portant un compte figé qui s'est périmé
— « 21 suites · 534 tests » alors que le run agrégé faisant autorité en disait davantage) :
Nsuites ·Mtests ·Mpassés ·Eéchec ·Eerreur · verdict →qa/regression/out/regression_run.json(compte agrégé faisant autorité, commité).
Et la fiche ERPNext Backend 03_agents/erpnext_backend/AGENT.md (ligne « source unique »,
même compte agrégé jadis saisi à la main qui s'était périmé — « 560 tests ») :
Ntests ·Msuites · verdict →qa/regression/out/regression_run.json(matrice de régression du repo, même source faisant autorité que la fiche QA).
Et — au grain le plus fin — la colonne « Tests » des tables de livrables de
toutes les fiches (03_agents/*/AGENT.md · crm, faisabilite, publiciste, qa) :
chaque cellule par suite était saisie à la main et se périmait dès qu'un test était
ajouté (fiche QA : acceptance 31 alors que la suite en portait 37 · audit_4big
35→34 · regression 25→26). Le compte agrégé ci-dessus (« 564 tests ») ne
suffit pas : une compensation entre deux suites (+1 / −1) laisserait la somme
juste et les deux lignes fausses. On recompute donc chaque cellule depuis
qa/regression/out/regression_plan.json (suites[path].test_methods) — et depuis
reglib.discovery.count_tests (même fonction que le plan · zéro duplication) pour le
self_module qa/regression, exclu de la matrice par SoD mais bien documenté.
Enfin — même classe, un cran au-dessus des cellules — les agrégats rédigés en
prose dans deux fiches (crm : « Total CRM 81 tests (25 + 31 + 25) » ;
erpnext_backend : « RBAC 60 tests (10 + 11 + 12 + 11 + 16) + e-CF 39 tests »).
Le total ET le multiset des composants (ordre-indépendant) sont recomputés depuis
suites[path].test_methods : une compensation entre deux suites laisserait la
table (gatée ci-dessus) juste et la prose fausse — c'est le même piège que les
cellules, au niveau agrégé.
Même classe encore, hors du domaine « tests » : les comptes par workspace
(cartes / liens / rôles) des portails frontend, transcrits à la main dans
deux docs — la table du README du module (frontend/portails/README.md) et
la prose de la fiche agent (03_agents/frontend_console/AGENT.md : « OTO Ventes
4 cartes/11 liens/12 rôles · … »). Chaque triplet et le total « N Workspace
natifs » sont recomputés depuis frontend/portails/out/MANIFEST.json (tableau
workspaces, lui-même byte-gaté par check_artifacts) : ajouter un rôle/DocType
au contrat RBAC ferait dériver les deux docs en silence sinon.
Même classe enfin sur les comptes RBAC des 3 volets (rbac/roleprofile_gen,
rbac/apply_plan, rbac/userperm_gen), recopiés à la main dans leurs READMEs
de module : la table « Nb rôles » par profil (+ total bijectif 50), la ligne
agrégée « Génération réelle : 6 étapes · 50 rôles / 116 DocPerm / 28 UP templates
/ 6 Role Profile · 4 DocType custom + 5 Company + 4 rôles equipe » (8 comptes de
tous les volets en une phrase) et la ventilation « 28 entite · 16 groupe · 2
own · 4 equipe ». Chaque nombre est recomputé depuis le out/MANIFEST.json
du volet (recalculé du contrat rbac_50_roles.json à chaque build, byte-gaté par
check_artifacts) : ajouter un rôle/DocType ferait dériver les trois READMEs
en silence sinon.
Même classe enfin côté SEO : le total et la ventilation par langue des
mots-clés (« 258 mots-clés (fr=87 · en=87 · es=84) ») étaient transcrits à
la main dans deux docs — la table « Livrables » du README de module
(05_deliverables_mvp/seo/README.md) et la même ligne de la fiche agent
(03_agents/seo/AGENT.md) — sans aucun gate. Le quadruplet (total + fr/en/es) est
recomputé depuis seo/out/MANIFEST.json (counts.keywords_total +
counts.keywords_per_lang, recomptés de projets_master.json à chaque build,
byte-gaté par check_artifacts). Recomputer les trois langues, pas seulement
le total, attrape une compensation entre langues (fr −1 / en +1) qui laisserait
le total juste et la ventilation fausse — ajouter un projet ferait sinon dériver
les deux docs en silence.
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.
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),
git rev-parse n'écrit rien → cd "" est un no-op qui RETOURNE SUCCÈS : le gate
poursuivait, git ls-files renvoyait une liste vide, et validate_json /
guard_constraints / check_docs sortaient exit 0 VERT en n'ayant RIEN
contrôlé — le pire « vert trompeur ». ci/lib.sh factorise ce préambule dans
cd_repo_root, qui échoue bruyamment (exit 3, code distinct d'un échec de
contrôle 1) hors d'un checkout git. check_ci_integrity (INV-B) prouve que
lib.sh est bien sourcé par des gates (pas du code mort) sans exiger qu'il figure
dans un job.
3. Exécution locale (avant push)
bash ci/guard_constraints.sh # contraintes CLAUDE.md
bash ci/validate_json.sh # schémas JSON
bash ci/check_docs.sh # liens + score
bash ci/check_artifacts.sh # reproductibilité out/ (build)
bash ci/check_regression.sh # fraîcheur matrice régression (run)
bash ci/check_ci_integrity.sh # intégrité du câblage CI (gate ⊇ tous jobs)
bash ci/check_readme_claims.sh # chiffres du README == artefacts cités (#6)
Chaque script retourne 0 si conforme, 1 sinon. Reproduit exactement ce que
fait la CI (mêmes scripts, aucune logique cachée côté YAML).
4. Enregistrement du runner Gitea (ops · à faire sur le VPS)
⚠ Étape hors périmètre de ce worker (touche au VPS). À réaliser par l'agent DevOps. Documenté ici pour traçabilité.
Le workflow cible un runner avec le label ubuntu-latest. Sur le VPS Gitea
(:3015), enregistrer un act_runner :
# Sur le VPS, récupérer le token runner :
# Gitea → Site Administration → Actions → Runners → Create new Runner
act_runner register \
--instance http://153.75.250.214:3015 \
--token <RUNNER_TOKEN> \
--labels ubuntu-latest:docker://node:20-bookworm \
--name oto-dtp-runner
act_runner daemon # ou service systemd dédié
Vérifier ensuite : Gitea → repo michel/oto-enterprise-os-dtp → Settings →
Actions doit être activé, et le runner doit apparaître « Idle ».
5. Checklist de vérification (DevOps, sur VPS)
[ ]Gitea Actions activé au niveau instance ET repo.[ ]act_runnerenregistré, labelubuntu-latest, statut Idle.[ ]Push de test → les 7 gates statiques apparaissent et passent au vert.[ ]PR de test avec violation volontaire →constraints-guardbloque (rouge).
Auto-score 4Big du livrable : 96/100. Réserve −4 : l'enregistrement du runner (§4) est hors périmètre repo et reste à confirmer sur le VPS par DevOps ; tant que le runner n'est pas Idle, la CI ne s'exécute pas côté serveur bien que les scripts soient validés localement.