Files
oto-enterprise-os-dtp/ci/README.md
T
Claude Code DTP Worker 97f859b6a5 [DTP-Worker] Sprint 8 · buffer L75 · Gate intégrité chiffres README (ci/check_readme_claims.sh)
Faux-vert réel : le README (point d'entrée) affichait des chiffres PÉRIMÉS
que sa propre source cité contredisait, sans qu'aucun gate ne les enforce.
check_docs ne valide que les LIENS, jamais la VALEUR des nombres → dérive
silencieuse : « 21/21 modules » (artefact: 22/22), « 21 suites » (22),
« 14 in_repo + 1 out_of_scope » (15 in_repo). CLAUDE.md #6 non gardé.

Fix : (1) corrige les 3 chiffres du README ; (2) 7e gate statique
check_readme_claims.sh qui RECOMPUTE chaque chiffre depuis l'artefact cité
(audit_4big/regression/acceptance + count AGENT.md) et exige l'égalité avec
l'écrit — un claim absent échoue aussi. Câblé : job check-readme-claims ∈
gate.needs (30 jobs), source lib.sh, verrouillé par check_ci_integrity INV-B.

Bite proofs : « 21/21 » réintroduit ⇒ exit 1 (README≠artefact) ; claim suites
retiré ⇒ exit 1 (INTROUVABLE) ; restauré ⇒ exit 0. Nettoyage : commentaires
CI « 21 suites/534 tests » → 22/551. 7/7 gates verts, YAML valide.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-31 03:10:19 +00:00

193 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 du README : chaque nombre de « État courant » == 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 les compteurs cités partout dans la doc et les logs
(« 21 suites · 534 tests · PASS »). Sans ce gate, ce chiffre pouvait se périmer en
silence (module + job CI ajoutés sans régénérer la matrice → « 21 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/<script>`) lui-même dans `gate.needs`, soit **sourcé** par ≥1 gate
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.
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** :
- modules gated `N/M` à `K/100` + verdict `PASS``qa/audit_4big/out/quality_report.json` (`totals`) ;
- `N` suites gated → `qa/regression/out/regression_plan.json` (`totals.suites`) ;
- `N` promesses (`X` sprint + `Y` métriques), `Z` `in_repo`, verdict `true``qa/acceptance/out/acceptance_matrix.json` ;
- « 13 agents » (×2 : nav + titre) → `git ls-files 03_agents/*/AGENT.md`.
Un claim **absent** du README é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 6 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
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` :
```bash
# 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_runner` enregistré, label `ubuntu-latest`, statut Idle.
- `[ ]` Push de test → les 6 gates statiques apparaissent et passent au vert.
- `[ ]` PR de test avec violation volontaire → `constraints-guard` bloque (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.