[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
+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=$?