[DTP-Worker] Sprint 8 · buffer · Fiches agents/colonne « Job CI » : chaque table de livrables de 03_agents/*/AGENT.md NOMME entre backticks le job Gitea Actions qui exécute la suite du module (rbac-tests · fiscal-ecf-tests · frontend-portails-tests…) — nom SAISI À LA MAIN dont la source de vérité est la clé jobs: de .gitea/workflows/ci.yml — mais AUCUN gate ne liait le NOM écrit dans la fiche au job réel. check_ci_integrity.sh prouve que chaque job est CÂBLÉ dans gate.needs (INV-A/B) et les blocs amont de check_readme_claims recomputent les COMPTES de tests par suite ; RIEN ne vérifiait que le token DÉSIGNE un job existant. Renommer un job dans ci.yml (rbac-testsrbac-role-tests) ou mal recopier demo-scenario-tests en demo-scenarios-tests laissait la fiche pointer un job FANTÔME pendant que le CI reste VERT (le vrai job tourne sous son nouveau nom) — la doc d'identité de l'agent ment en silence.

Gate ajouté (bloc « Fiches agents · colonne Job CI ») : on RECOMPUTE l'ensemble des jobs depuis la clé `jobs:` de ci.yml (la section `on:` push/pull_request/workflow_dispatch est AVANT `jobs:` et donc exclue ; jamais une liste à la main) et on exige que chaque token `*-tests` cité dans une fiche y figure. Direction fiche→ci.yml (le consommateur) : ci.yml peut définir des jobs non cités (légitime), une fiche ne peut pas citer un job absent ; la disparition totale de la colonne échoue AUSSI. 33 références vérifiées == jobs ci.yml ; 2 morsures (token de fiche `seo-tests`→`seo-suite-tests` fantôme · job ci.yml `rbac-tests` renommé → mord SIMULTANÉMENT les 4 fiches qui le citent : erpnext_backend/ifc_speckle/mobile/rendu), restauré vert, 7 gates re-verts. ci/README.md + activity log MAJ.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Code DTP Worker
2026-08-01 03:38:42 +00:00
parent e62ecfd61e
commit d7f727203c
3 changed files with 136 additions and 1 deletions
+43
View File
@@ -414,3 +414,46 @@ contrat · exit 0. Working tree byte-restauré (`git checkout --`, **JAMAIS**
édition **hors** `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact ; le seed réel
du rôle + le câblage Compupar restent côté VPS).
- **Auto-score 4Big** : 96/100.
## Session `20260801_033217` · Buffer S8 · Domaine Fiches agents/colonne « Job CI » : chaque table de livrables de `03_agents/*/AGENT.md` porte une colonne **Job CI** qui NOMME entre backticks le job Gitea Actions exécutant la suite du module (`rbac-tests`, `fiscal-ecf-tests`, `frontend-portails-tests`…). Ce nom est **saisi à la main** ; la source de vérité est la clé `jobs:` de `.gitea/workflows/ci.yml`. Ce lien n'était couvert par **AUCUN gate** : `check_ci_integrity.sh` prouve que chaque job est CÂBLÉ dans `gate.needs` (INV-A/B) et les blocs amont de `check_readme_claims` recomputent les **comptes** de tests par suite — mais RIEN ne vérifiait que le nom ÉCRIT dans la fiche DÉSIGNE un job réel.
**Tâche** : **Sprint 8 · buffer** (DevOps CI/CD · QA). Roadmap fonctionnellement
close ; poursuite de la série anti-dérive (CLAUDE.md #6). Même classe de « vert
trompeur » que les cellules « Tests » par suite ou la colonne « Type » des Role
Profile — appliqué à la **surface data-derived distincte** : le NOM de job cité par
la fiche, jamais confronté à la définition réelle du workflow CI.
**Dérive silencieuse fermée** :
- `03_agents/*/AGENT.md` — colonne « Job CI » : 33 tokens `*-tests` répartis sur
13 fiches, chacun un nom de job Gitea Actions recopié à la main.
- Source faisant autorité : la section `jobs:` de `.gitea/workflows/ci.yml`.
- Piège : renommer un job dans ci.yml (`rbac-tests``rbac-role-tests`) ou mal
recopier `demo-scenario-tests` en `demo-scenarios-tests` laisse la fiche pointer
un job **fantôme** — le CI reste **vert** (le vrai job tourne sous son nouveau
nom), la doc d'identité de l'agent ment en silence. `check_ci_integrity` ne
regarde QUE le câblage `gate.needs`, aveugle au NOM cité par la prose.
**Gate ajouté** (`ci/check_readme_claims.sh`, bloc « Fiches agents · colonne
Job CI ») : on RECOMPUTE l'ensemble des jobs depuis la clé `jobs:` de ci.yml
(la section `on:` — push/pull_request/workflow_dispatch — est AVANT `jobs:` et donc
naturellement exclue ; jamais une liste à la main) et on exige que chaque token
`*-tests` cité dans une fiche y figure. Direction **fiche→ci.yml** (le
consommateur) : ci.yml peut définir des jobs non cités (légitime), mais une fiche
ne peut pas citer un job absent. La disparition totale de la colonne échoue AUSSI
(le recensement qui s'évapore est lui-même une régression).
**Morsures vérifiées (2)** : (1) token de fiche `seo-tests``seo-suite-tests`
(job fantôme, mord les 2 citations dans `seo/AGENT.md`) ; (2) job ci.yml
`rbac-tests` renommé `rbac-role-tests` → mord **SIMULTANÉMENT les 4 fiches** qui
le citent (`erpnext_backend`/`ifc_speckle`/`mobile`/`rendu`). État courant :
**33 références == jobs ci.yml**, exit 0. Working tree byte-restauré
(`cp` depuis backup + `git checkout` implicite — **JAMAIS** `git clean`, interdit
absolu). Sibling gates re-verts : `check_ci_integrity`/`check_docs`/
`guard_constraints`/`check_artifacts` = PASS.
- `ci/README.md` (table récap du pipeline + paragraphe détaillé « Colonne Job CI
des fiches agents == section `jobs:` de ci.yml ») mis à jour.
- **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo ;
édition **hors** `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact ; aucun
déclenchement de workflow CI réel — analyse statique du YAML uniquement).
- **Auto-score 4Big** : 96/100.
+22 -1
View File
File diff suppressed because one or more lines are too long
+71
View File
@@ -3702,6 +3702,77 @@ if b4 is not None:
+ (f" · absents={miss}" if miss else "")
+ (f" · en trop={extra}" if extra else "") + " → aligner le test")
# ============================================================================
# Fiches agents · colonne « Job CI » — chaque nom de job cité DOIT exister dans
# .gitea/workflows/ci.yml (même classe de « vert trompeur », côté câblage CI).
# ----------------------------------------------------------------------------
# Chaque table de livrables des fiches 03_agents/*/AGENT.md porte une colonne
# « Job CI » qui NOMME, mot pour mot et entre backticks, le job Gitea Actions qui
# exécute la suite du module (`rbac-tests`, `fiscal-ecf-tests`, …). Ce nom est
# SAISI À LA MAIN. La source de vérité est la section `jobs:` de ci.yml. Les
# gates existants ne couvraient PAS ce lien : `check_ci_integrity.sh` prouve que
# chaque job de ci.yml est bien CÂBLÉ dans `gate.needs` (INV-A/B), et les blocs
# amont recomputent les COMPTES de tests par suite — mais RIEN ne vérifiait que
# le nom écrit dans la fiche DÉSIGNE un job réel. Renommer un job dans ci.yml
# (ex. `rbac-tests` → `rbac-role-tests`, ou `demo-scenario-tests` mal recopié en
# `demo-scenarios-tests`) laisse la fiche pointer un job FANTÔME : le CI reste
# vert (le vrai job tourne sous son nouveau nom), la doc d'identité de l'agent
# ment en silence. On RECOMPUTE ici l'ensemble des jobs depuis la section `jobs:`
# de ci.yml (jamais une liste à la main) et on exige que chaque token `*-tests`
# cité dans une fiche y figure. Direction fiche→ci.yml (le consommateur) : ci.yml
# peut définir des jobs non cités (légitime), mais une fiche ne peut pas citer un
# job absent.
# ============================================================================
CI_YML = ".gitea/workflows/ci.yml"
try:
ci_lines = open(CI_YML, encoding="utf-8").read().splitlines()
except OSError as e:
bad(f"Job CI fiches · {CI_YML} illisible : {e}")
ci_lines = None
if ci_lines is not None:
# Ensemble des jobs = clés à 2 espaces sous la clé top-level `jobs:`
# (la section `on:` — push/pull_request/workflow_dispatch — est AVANT `jobs:`
# et donc naturellement exclue). Une clé top-level `^\S` clôt la section.
jobs = set()
in_jobs = False
for line in ci_lines:
if re.match(r"^jobs:\s*$", line):
in_jobs = True
continue
if in_jobs and re.match(r"^\S", line): # nouvelle clé top-level
break
if in_jobs:
mj = re.match(r"^ ([A-Za-z0-9_-]+):\s*$", line)
if mj:
jobs.add(mj.group(1))
if not jobs:
bad(f"Job CI fiches · aucun job détecté dans la section `jobs:` de "
f"{CI_YML} — parse cassé ou fichier vidé (régression de câblage).")
fiches_ci = subprocess.run(
["git", "ls-files", "03_agents/*/AGENT.md"],
capture_output=True, text=True).stdout.split()
tok_re = re.compile(r"`([a-z0-9]+(?:-[a-z0-9]+)*-tests)`")
seen_jobrefs = 0
for af in fiches_ci:
try:
flines = open(af, encoding="utf-8").read().splitlines()
except OSError as e:
bad(f"Job CI fiches · {af} illisible : {e}"); continue
for ln, line in enumerate(flines, 1):
for tok in tok_re.findall(line):
seen_jobrefs += 1
if jobs and tok in jobs:
good(f"Job CI · {af}:{ln} · `{tok}` == job ci.yml")
elif jobs:
bad(f"Job CI · {af}:{ln} · `{tok}` ABSENT de la section "
f"`jobs:` de {CI_YML} → job renommé/supprimé, fiche "
f"pointe un job fantôme (régénérer la fiche ou ci.yml)")
if seen_jobrefs == 0:
bad("Job CI fiches · AUCUN token `*-tests` trouvé dans les fiches — la "
"disparition de la colonne « Job CI » est elle-même une régression.")
sys.exit(1 if FAIL else 0)
PY
rc=$?