"""Le deploiement web doit refuser de construire sans l'artefact du garde-fou CPU. Mesure le 2026-09-07 : `prompt_guard_char_ngram.joblib` n'etait dans AUCUNE image de prod. Il est gitignore, son seul chemin est le bucket `animetix-deploy-assets`, et personne ne l'y avait jamais mis -- le script de synchro refuse de tourner SANS lui (pour ne pas l'effacer), mais rien ne refusait de DEPLOYER sans lui. Un mois de deploiements verts, et l'etage CPU concu le 2026-08-07 (89,5 % du trafic benin sans appel LLM) est reste inerte : toute requete partait au LLM moderateur, avec toute la liste de categories. Le design (section 7.2) veut que l'etage se degrade proprement quand l'artefact manque au RUNTIME -- un fichier corrompu ne doit pas faire tomber le web. Ce n'est pas une raison pour le laisser manquer au BUILD : la, on sait, on peut refuser, et refuser avant `builds submit` evite de payer 2 h de build pour livrer un garde-fou vide. """ import pathlib import re CI = ( pathlib.Path(__file__).resolve().parents[2] / ".github" / "workflows" / "ci.yml" ).read_text(encoding="utf-8") ARTIFACT = "prompt_guard_char_ngram.joblib" def _deploy_to_prod_job(): start = CI.index(" deploy-to-prod:") end = CI.index("\n deploy-brain-to-prod:") return CI[start:end] def test_the_web_deploy_checks_the_bucket_for_the_cpu_artifact(): job = _deploy_to_prod_job() assert ARTIFACT in job, ( "deploy-to-prod ne mentionne pas l'artefact du garde-fou CPU : il " "construira et livrera une image sans lui, comme pendant un mois" ) assert re.search( r"gsutil\s+(-q\s+)?(ls|stat)\s+[^\n]*" + re.escape(ARTIFACT), job ), ( "la presence doit etre verifiee dans le BUCKET (gsutil ls/stat), " "pas dans le checkout : le fichier est gitignore, il n'y est jamais" ) def test_the_check_runs_before_the_build(): job = _deploy_to_prod_job() assert job.index(ARTIFACT) < job.index("gcloud builds submit"), ( "verifier apres le build, c'est payer ~2 h de Cloud Build pour " "apprendre ce qu'on savait avant" ) def test_a_missing_artifact_fails_the_deploy(): """Un `gsutil ls` seul journalise et continue ; la sortie 1 est ce qui rougit le workflow. Sans elle, le test precedent serait decoratif.""" job = _deploy_to_prod_job() steps = re.split(r"\n - name: ", job) step = next(s for s in steps if ARTIFACT in s) assert "exit 1" in step