fix(parity): preuve WIF 3 chemins rejouable de bout en bout (P0-A2-09) - #46
Conversation
…e le refus §D1 - repro.sh: exécutable end-to-end (provision projet test → configure ET JOUE les 3 chemins avec assertions strictes → teardown), plus rien de commenté - .github/workflows/wif-proof.yml: paramétré par inputs workflow_dispatch (project_id/number, bucket, SAs, préfixe) → rejouable après teardown ; autorisé exige HTTP 200 + contenu (échec sur 403/404), négatif = impersonation DENIED - chemin GKE: vrai cluster de test + pod négatif RÉEL (KSA non liée) joué et archivé - Dockerfile Cloud Run: COPY cloudrun-main.py main.py (nom corrigé) - .gitignore: exception !docs/deploy-evidence/** (les *.log de preuve étaient avalés) - P0-A2-09: resolutionBranch/resolutionStatus PROVEN_REVIEW_PENDING, evidenceId repointé, status reste OPEN (NON clôturé) ; manifest régénéré P0-A2-09 reste PROVEN_REVIEW_PENDING — pas de merge sans feu vert. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… (P0-A2-09)
Preuve rejouable jouée de bout en bout sur un projet de test FRAIS
(ecode-wif-proof-710401, créé après teardown du run précédent → prouve la
rejouabilité), zéro clé, teardown joué, projet DELETE_REQUESTED, coût ~0$ :
- Cloud Run : autorisé read_status=200 + contenu / négatif SA sans droit = 403
- GitHub OIDC : run 29906107467 success — external_account (keyless), READ_HTTP=200
+ contenu / négatif impersonation SA non autorisée = AUTH failure
- GKE WIF : pod KSA liée → READ_HTTP=200 + contenu ; pod KSA NON liée → identité
= défaut du pool (≠ GSA autorisée) ET lecture bucket REFUSÉE 403 (réel, archivé)
Négatif GKE renforcé : le pod non lié tente réellement la lecture et archive le 403
(au lieu d'une simple assertion). Artefacts suivis dans git (exception .gitignore
!docs/deploy-evidence/**). repro.sh: retry propagation IAM (SA fraîches).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2af261cfff
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| mkdir -p "$OUT" | ||
| log(){ echo "[$(date -u +%H:%M:%S)] $*" | tee -a "$OUT/run.log"; } | ||
| Q(){ grep -vE "WARNING|Python 3|reinstall|CLOUDSDK|compatible Python|gcloud components|NotOpenSSL|warnings.warn|urllib3|importlib|^\s*$" || true; } | ||
| fail(){ echo "ASSERTION FAILED: $*" | tee -a "$OUT/run.log"; exit 1; } |
There was a problem hiding this comment.
Ensure cleanup runs after every failure
Any assertion or command failure after the project is provisioned exits through fail before reaching the teardown block at lines 191–202. For example, a failed GKE authorization check leaves the cluster, Cloud Run service, Artifact Registry repository, and billing-enabled project active, so the script's failure path can continue incurring costs indefinitely; register an EXIT/ERR cleanup handler immediately after provisioning rather than running cleanup only on the success path.
Useful? React with 👍 / 👎.
| else | ||
| log " gh absent — chemin 2 à déclencher manuellement : gh workflow run wif-proof.yml --ref $GH_REF -f project_id=$PROJECT -f project_number=$PN -f bucket=$BUCKET -f authorized_sa=$AUTH_SA -f wrong_sa=$WRONG_SA -f expect_prefix=$EXPECT" | ||
| fi |
There was a problem hiding this comment.
Fail instead of skipping the GitHub OIDC path
When gh is absent, this branch merely logs a manual command and continues through GKE and teardown before printing that all three paths were proven. Thus a successful exit can omit path 2 entirely even though the script's contract says a green run proves all three paths; treat missing or unauthenticated gh as a prerequisite failure before provisioning.
AGENTS.md reference: AGENTS.md:L12-L13
Useful? React with 👍 / 👎.
| [ -n "$NEG_CODE" ] || fail "GKE négatif : pas de tentative de lecture archivée" | ||
| [ "$NEG_CODE" = "200" ] && fail "GKE négatif : la KSA non liée a LU le bucket (HTTP 200, inattendu)" | ||
| case "$NEG_ID" in *"${AUTH_SA}"*) fail "GKE négatif : la KSA non liée a obtenu la GSA autorisée (inattendu)";; esac |
There was a problem hiding this comment.
Require an authorization denial in the GKE negative check
The negative proof accepts every status except 200, so a DNS failure, timeout (curl reports 000), missing object (404), or server error (5xx) is recorded as an access-control refusal. Under those transient failure conditions the run can claim that the unbound KSA was denied without receiving an authorization response; require the expected 401/403 status and a nonempty unexpected identity before marking this path proven.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Pull request overview
Cette PR vise à rendre rejouable de bout en bout la preuve des 3 chemins Workload Identity (sans clé de service) pour le sujet de parité P0-A2-09, en versionnant un script de repro (provision → run → assertions → teardown), un workflow GitHub Actions paramétrable, et des artefacts de preuve “live”.
Changes:
- Ajout d’un dossier d’évidence
docs/deploy-evidence/...avecrepro.sh, un workflow dispatchablewif-proof.yml, et des traces d’exécution/teardown. - Mise à jour du registre/parity (P0) pour pointer vers la nouvelle evidence + statut
PROVEN_REVIEW_PENDING. - Ajustement
.gitignorepour versionner les artefacts d’évidence malgré*.log.
Reviewed changes
Copilot reviewed 22 out of 26 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| docs/parity/P0_REGISTRY.yaml | Pointe P0-A2-09 vers la nouvelle evidence + statut/branche de résolution. |
| docs/parity/DOCUMENT_MANIFEST.yaml | Met à jour le hash du document registry. |
| docs/deploy-evidence/2026-07-21-wif-three-paths/teardown-trace.txt | Trace teardown (1er run) archivée. |
| docs/deploy-evidence/2026-07-21-wif-three-paths/repro.sh | Script repro/provision/run/assert/teardown des 3 chemins. |
| docs/deploy-evidence/2026-07-21-wif-three-paths/replay-20260722T085317Z/teardown-trace.txt | Trace teardown (run rejoué) archivée. |
| docs/deploy-evidence/2026-07-21-wif-three-paths/replay-20260722T085317Z/secret.txt | Contenu “secret” utilisé pour les assertions (run rejoué). |
| docs/deploy-evidence/2026-07-21-wif-three-paths/replay-20260722T085317Z/run.log | Log d’exécution du script (run rejoué). |
| docs/deploy-evidence/2026-07-21-wif-three-paths/replay-20260722T085317Z/policy2.json | IAM policy modifiée (audit configs) archivée. |
| docs/deploy-evidence/2026-07-21-wif-three-paths/replay-20260722T085317Z/policy.json | IAM policy initiale archivée. |
| docs/deploy-evidence/2026-07-21-wif-three-paths/replay-20260722T085317Z/path3-cloudrun-negative.json | Preuve Cloud Run négative (403) archivée. |
| docs/deploy-evidence/2026-07-21-wif-three-paths/replay-20260722T085317Z/path3-cloudrun-authorized.json | Preuve Cloud Run autorisée (200+contenu) archivée. |
| docs/deploy-evidence/2026-07-21-wif-three-paths/replay-20260722T085317Z/path2-github-oidc.txt | Résumé du run GitHub OIDC (run rejoué). |
| docs/deploy-evidence/2026-07-21-wif-three-paths/replay-20260722T085317Z/path2-github-oidc.log | Log complet du run GitHub Actions (run rejoué). |
| docs/deploy-evidence/2026-07-21-wif-three-paths/replay-20260722T085317Z/path1-gke-negative.txt | Preuve GKE négative archivée (run rejoué). |
| docs/deploy-evidence/2026-07-21-wif-three-paths/replay-20260722T085317Z/path1-gke-authorized.txt | Preuve GKE autorisée archivée (run rejoué). |
| docs/deploy-evidence/2026-07-21-wif-three-paths/replay-20260722T085317Z/params.env | Paramètres du run (project/bucket) archivé. |
| docs/deploy-evidence/2026-07-21-wif-three-paths/replay-20260722T085317Z/docker-build.log | Log build/push Docker archivé. |
| docs/deploy-evidence/2026-07-21-wif-three-paths/README.md | Documentation de la preuve + mode de reproduction. |
| docs/deploy-evidence/2026-07-21-wif-three-paths/path3-cloudrun-negative.json | Preuve Cloud Run négative (1er run) archivée. |
| docs/deploy-evidence/2026-07-21-wif-three-paths/path3-cloudrun-authorized.json | Preuve Cloud Run autorisée (1er run) archivée. |
| docs/deploy-evidence/2026-07-21-wif-three-paths/path2-github-oidc.txt | Résumé GitHub OIDC (1er run) archivé. |
| docs/deploy-evidence/2026-07-21-wif-three-paths/path1-gke-recited.txt | Trace “recited” GKE (référence) archivée. |
| docs/deploy-evidence/2026-07-21-wif-three-paths/cloudrun-main.py | Serveur Cloud Run minimal lisant GCS via metadata token (preuve chemin 3). |
| docs/deploy-evidence/2026-07-21-wif-three-paths/cloudrun-Dockerfile | Dockerfile Cloud Run corrigé (COPY cloudrun-main.py → main.py). |
| .gitignore | Unignore docs/deploy-evidence/** malgré *.log. |
| .github/workflows/wif-proof.yml | Workflow dispatchable paramétrable prouvant le chemin GitHub OIDC (chemin 2). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| { | ||
| "members": [ | ||
| "user:groupequaliwatt@gmail.com" | ||
| ], | ||
| "role": "roles/owner" | ||
| }, |
| @@ -0,0 +1 @@ | |||
| {"bindings": [{"members": ["serviceAccount:222165552979@cloudservices.gserviceaccount.com"], "role": "roles/compute.instanceGroupManagerServiceAgent"}, {"members": ["serviceAccount:service-222165552979@compute-system.iam.gserviceaccount.com"], "role": "roles/compute.serviceAgent"}, {"members": ["serviceAccount:service-222165552979@container-engine-robot.iam.gserviceaccount.com"], "role": "roles/container.serviceAgent"}, {"members": ["serviceAccount:service-222165552979@containerregistry.iam.gserviceaccount.com"], "role": "roles/containerregistry.ServiceAgent"}, {"members": ["serviceAccount:service-222165552979@cloud-filer.iam.gserviceaccount.com"], "role": "roles/file.serviceAgent"}, {"members": ["user:groupequaliwatt@gmail.com"], "role": "roles/owner"}, {"members": ["serviceAccount:service-222165552979@gcp-sa-pubsub.iam.gserviceaccount.com"], "role": "roles/pubsub.serviceAgent"}, {"members": ["serviceAccount:service-222165552979@serverless-robot-prod.iam.gserviceaccount.com"], "role": "roles/run.serviceAgent"}], "etag": "BwZXL0ePCMw=", "version": 1, "auditConfigs": [{"service": "allServices", "auditLogConfigs": [{"logType": "DATA_READ"}, {"logType": "DATA_WRITE"}]}]} No newline at end of file | |||
| log "0. idempotence : projets ecode-wif-proof-* ACTIFS ?" | ||
| EXISTING=$(gcloud projects list --filter="projectId:ecode-wif-proof-* AND lifecycleState:ACTIVE" --format="value(projectId)" 2>/dev/null | Q | head -1 || true) | ||
| if [ -n "$EXISTING" ]; then PROJECT="$EXISTING"; log " réutilise $PROJECT"; else | ||
| PROJECT="ecode-wif-proof-$(date +%s | tail -c 7)" | ||
| log " crée $PROJECT sous folder $FOLDER" | ||
| gcloud projects create "$PROJECT" --folder="$FOLDER" --name="ECode WIF Proof" 2>&1 | Q | tail -1 | ||
| gcloud billing projects link "$PROJECT" --billing-account="$BILLING" 2>&1 | Q | tail -1 | ||
| fi |
| gh workflow run wif-proof.yml --ref "$GH_REF" -R "$REPO" \ | ||
| -f project_id="$PROJECT" -f project_number="$PN" -f bucket="$BUCKET" \ | ||
| -f authorized_sa="$AUTH_SA" -f wrong_sa="$WRONG_SA" -f expect_prefix="$EXPECT" 2>&1 | Q | tail -1 \ | ||
| || fail "gh workflow run (le workflow doit exister sur $GH_REF et être dispatchable)" | ||
| sleep 12 | ||
| RUN=$(gh run list --workflow=wif-proof.yml -R "$REPO" --limit 1 --json databaseId --jq '.[0].databaseId') | ||
| log " run GitHub Actions=$RUN, attente..." |
| # négatif RÉEL joué : la lecture du bucket depuis la KSA non liée est REFUSÉE (≠ 200) ET l'identité n'est PAS la GSA autorisée | ||
| [ -n "$NEG_CODE" ] || fail "GKE négatif : pas de tentative de lecture archivée" | ||
| [ "$NEG_CODE" = "200" ] && fail "GKE négatif : la KSA non liée a LU le bucket (HTTP 200, inattendu)" | ||
| case "$NEG_ID" in *"${AUTH_SA}"*) fail "GKE négatif : la KSA non liée a obtenu la GSA autorisée (inattendu)";; esac | ||
| log " ✓ GKE négatif RÉEL : identité=$NEG_ID (≠ GSA autorisée), lecture bucket REFUSÉE HTTP=$NEG_CODE (archivé)" |
| steps: | ||
| - id: auth | ||
| name: Federate GitHub OIDC -> impersonate AUTHORIZED SA (eager token, no key) | ||
| uses: google-github-actions/auth@v2 | ||
| with: | ||
| project_id: ${{ inputs.project_id }} | ||
| workload_identity_provider: projects/${{ inputs.project_number }}/locations/global/workloadIdentityPools/github-pool/providers/github | ||
| service_account: ${{ inputs.authorized_sa }} | ||
| token_format: access_token |
| steps: | ||
| - id: auth | ||
| name: NEGATIVE — try to impersonate an UN-GRANTED SA (eager token must be DENIED) | ||
| continue-on-error: true | ||
| uses: google-github-actions/auth@v2 | ||
| with: | ||
| project_id: ${{ inputs.project_id }} | ||
| workload_identity_provider: projects/${{ inputs.project_number }}/locations/global/workloadIdentityPools/github-pool/providers/github | ||
| service_account: ${{ inputs.wrong_sa }} | ||
| token_format: access_token |
…xecFileSync) pour rendre la CI verte
repro.sh + wif-proof.yml durcis : 1. PRÉFLIGHT : gh (authentifié) + gcloud/docker/kubectl/curl OBLIGATOIRES avant tout provisioning — le script échoue si gh absent/non authentifié. 2. run GitHub suivi par NONCE EXACT : dispatch avec -f nonce ; run-name porte le nonce ; repro.sh récupère le databaseId dont displayTitle contient le nonce (jamais « le plus récent »), et re-vérifie le nonce sur le run choisi. 3. négatif GKE : exige un refus IAM PRÉCIS 401/403 + corps permission-denied contrôlé ; un 000/404/5xx échoue (ne prouve pas un refus IAM). 4. trap teardown installé DÈS la création du projet (trap EXIT idempotent) → aucune ressource/coût laissé sur erreur intermédiaire. Réplay + archive du nouveau run à suivre. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-09) Rejoué de bout en bout sur projet frais ecode-wif-proof-806836 avec les 4 corrections fail-closed (préflight gh obligatoire ; run GitHub suivi par NONCE exact 30004371924 ; négatif GKE 403 + corps permission-denied contrôlé ; trap teardown dès création projet). Cloud Run 200/403 ; GitHub OIDC nonce-vérifié success ; GKE négatif refus IAM précis ; teardown joué (PROJECT_STATE=DELETE_REQUESTED, 0 projet actif ~0$). replay-20260723T114035Z/ archivé ; gitleaks allowlist des captures test éphémères. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…20260723-CODEX-07) + rejoué live Refus expert RR-20260723-CODEX-07 §P0-A2-09 : dans repro.sh le `trap teardown EXIT` était armé APRÈS `gcloud projects create` ET `gcloud billing projects link`. Sous `set -Eeuo pipefail`, un échec de la liaison billing faisait sortir le script AVANT l'armement du trap → projet laissé ACTIF (ressource facturable orpheline). Correction minimale (verbatim expert) : 1) ID projet calculé d'abord ; `trap teardown EXIT` armé AVANT create ET billing link (repro.sh : teardown() l.67, trap l.85, create l.93, billing link l.94). 2) teardown idempotent ET SÛR si le projet n'existe pas encore (garde `gcloud projects describe` → PROJECT_STATE=ABSENT, aucune erreur). 3) rejeu live après correctif + trace du cas négatif billing-fail archivée. Preuves live (2026-07-23, zéro clé, ~0$) : - Cas négatif billing-fail : replay-20260723T191146Z-negative-billingfail/ — WIF_BILLING invalide → projet ecode-wif-proof-833908 créé, billing link échoue IAM_PERMISSION_DENIED sous set -e, trap EXIT nettoie → PROJECT_STATE=DELETE_REQUESTED (describe indépendant = DELETE_REQUESTED, 0 projet ACTIF). Voir NEGATIVE-CASE-README.md. - 3 chemins rejoués : replay-20260723T191340Z/ (projet frais ecode-wif-proof-834022) — Cloud Run 200/403 ; GitHub OIDC run 30037477577 nonce-vérifié success ; GKE autorisé 200+contenu / négatif 403+corps permission-denied identité≠GSA ; teardown joué, PROJECT_STATE=DELETE_REQUESTED. - Diff exact de l'ordre trap↔create : trap-order-fix.diff. P0_REGISTRY proof (item d) corrigé ; DOCUMENT_MANIFEST régénéré ; registres valides. resolutionStatus reste PROVEN_REVIEW_PENDING (pas de clôture sans re-signature). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…RR-20260723-CODEX-08) Refus RR-20260723-CODEX-08 §P0-A2-09 : dans teardown(), la garde `if gcloud projects describe … then … else ABSENT` lisait TOUT échec de describe (réseau/API/auth/quota) comme « projet absent » → suppression sautée → un flap pouvait laisser le projet ACTIF. La preuve billing-fail ne couvrait que le cas où describe répond bien. Correction (logique extraite dans teardown-lib.sh, sourcée par repro.sh), verbatim : 1) classify_project_state parse le motif d'erreur : NOT_FOUND conclu SEULEMENT sur motif not-found ET auth prouvée saine (gcloud auth print-access-token) ; sinon UNKNOWN (transitoire/permission/auth non prouvée). 2) describe réessayé sur erreurs transitoires (retry borné). 3) gcloud projects delete TENTÉ même quand l'état est UNKNOWN (illisible). 4) reçu fail-closed : CLEANUP_RECEIPT=OK seulement si état final DELETE_REQUESTED ou NOT_FOUND authentifié ; sinon CLEANUP_RECEIPT=FAILED + exit != 0. 5) test négatif à mock gcloud (teardown-lib.spec.sh) simulant une erreur transitoire de describe : vérifie que le delete est tenté quand même ET que le reçu échoue fail-closed. PASS=23 FAIL=0 (rr08-teardown-faultinjection/spec-output.txt). Durcissement reproductibilité : création des pods GKE via krun_pod (retry + capture stderr) — un kubectl run échouant silencieusement donnait « pods not found » → faux échec de chemin. Rejoué LIVE 2026-07-31 (nouveau teardown, zéro clé, ~0$) : - négatif billing-fail replay-20260731T151804Z-rr08-negative-billingfail/ : DESCRIBE_CLASSIFICATION=PRESENT:ACTIVE → delete → DELETE_REQUESTED, CLEANUP_RECEIPT=OK. - 3 chemins replay-20260731T154358Z-rr08/ (projet frais ecode-wif-proof-512642) : Cloud Run 200/403 ; GitHub OIDC run 30645065425 nonce-vérifié success ; GKE autorisé 200+contenu / négatif 403+corps permission-denied identité≠GSA ; teardown fail-closed CLEANUP_RECEIPT=OK PROJECT_STATE=DELETE_REQUESTED. Projets = DELETE_REQUESTED (describe). P0_REGISTRY proof + README mis à jour ; DOCUMENT_MANIFEST régénéré ; validate-registries vert. resolutionStatus reste PROVEN_REVIEW_PENDING (pas de clôture sans re-signature). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…A2-09, RR-20260723-CODEX-09) Refus RR-20260723-CODEX-09 §P0-A2-09 : le critère NOTFOUND de RR-08 concluait « absent » sur le message ambigu « project not found or permission denied » dès que `gcloud auth print-access-token` réussissait. Or un jeton valide prouve l'AUTH, pas l'AUTORISATION projects.get : un principal authentifié SANS droit de lecture sur un projet EXISTANT reçoit ce même message → aurait été classé « absent » → delete sauté. Correction (teardown-lib.sh) — classify_project_state interroge Cloud Resource Manager v1 projects.get et classe sur le STATUT HTTP STRUCTURÉ, jamais sur le texte : - 200 → PRESENT:<lifecycleState> - 404 ET error.status=NOT_FOUND → NOTFOUND (vrai absent, non ambigu) - 401/403/404-sans-statut → UNKNOWN (jamais « absent ») - 000/408/429/5xx → transitoire → retry borné → UNKNOWN delete tenté même en UNKNOWN ; reçu OK seulement si état final DELETE_REQUESTED ou NOT_FOUND structuré, sinon FAILED + exit != 0. Fait GCP vérifié live : GET /v1/projects/<inexistant> renvoie 403 PERMISSION_DENIED (pas 404) → l'ambiguïté 403 reste UNKNOWN, delete tenté, reçu fail-closed. Test (mocks gcloud + curl) teardown-lib.spec.sh : PASS=31 FAIL=0. Cas RR-09 : - ambiguous_403 : 403 « not found or permission denied » → PAS NOTFOUND, reçu FAILED ; - exists_no_getdelete (EXIGÉ) : jeton valide + projet EXISTANT + principal sans projects.get NI projects.delete (GET 403, delete 403) → CLEANUP_RECEIPT=FAILED, PAS OK ; - ambiguous_404_no_status → PAS NOTFOUND ; notfound_structured_404 = seul cas NOTFOUND. Rejoué LIVE 2026-08-03 (teardown RR-09 REST, zéro clé, ~0$) : - négatif billing-fail replay-20260803T064401Z-rr09-negative-billingfail/ : DESCRIBE_CLASSIFICATION=PRESENT:ACTIVE (CRM 200) → delete → DELETE_REQUESTED, RECEIPT=OK ; - 3 chemins replay-20260803T064443Z-rr09/ (projet frais ecode-wif-proof-739484) : Cloud Run 200/403 ; GitHub OIDC run 30791438920 nonce-vérifié success ; GKE autorisé 200+contenu / négatif 403+corps identité≠GSA ; teardown fail-closed RECEIPT=OK DELETE_REQUESTED. Corrige aussi une réf de dossier obsolète (151929Z→154358Z) dans le bloc RR-08 du README. P0_REGISTRY proof + README MAJ ; DOCUMENT_MANIFEST régénéré ; validate-registries vert. resolutionStatus reste PROVEN_REVIEW_PENDING (pas de clôture sans re-signature). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix(parity): trap teardown WIF armé AVANT create/billing (A2-09, RR-20260723-CODEX-07)
…uve complète branche + gouvernance RR-10 de main), manifeste régénéré
Objet — corriger le refus « preuve WIF non rejouable » (P0-A2-09, REPONSE_EXPERT_PR40 §D1)
Re-prouve LIVE les 3 chemins Workload Identity, zéro clé de service, désormais rejouables de bout en bout :
repro.shprovisionne un projet de test frais (jamais la prod), configure ET JOUE réellement les 3 chemins avec assertions strictes, puis teardown complet. Coût ~0 $.Les 5 défauts du refus §D1, corrigés
repro.shprovisionnait puis commentait les 3 cheminswif-proof.ymlparamétré par inputsworkflow_dispatch(project/number/bucket/SAs/préfixe) → rejouable après teardowncurlautorisé ne rougissait pas sur 403/404COPY main.py .(fichier =cloudrun-main.py)COPY cloudrun-main.py main.py.gitignore(*.log) avalait les preuves → exception!docs/deploy-evidence/**ajoutée ; les 3.logde preuve sont bien suivis dans git.Preuves LIVE rejouables (run frais
ecode-wif-proof-710401, créé APRÈS teardown du run précédent)Dossier
docs/deploy-evidence/2026-07-21-wif-three-paths/replay-20260722T085317Z/:read_status=200+ contenu (keyUsed:false) / négatif SA sans droit403.external_account(fédéré, pas une clé),READ_HTTP=200+ contenu / négatif impersonation SA non autoriséeAUTH_STEP_OUTCOME=failure.READ_HTTP=200+ contenu ; pod KSA non liée → identité = défaut du pool (≠ GSA autorisée) ET lecture bucket refusée403(réel, archivé).DELETE_REQUESTED. 0 projet actif restant (coût ~0 $).Sécurité
Projet de TEST dédié sous folder
ecode-factory-test— jamaisvibecore-495216. Aucune clé de service créée nulle part (identités via metadata / OIDC fédéré). Audit Data Access activé. Aucun token en clair dans les artefacts.Statut
P0-A2-09 reste
PROVEN_REVIEW_PENDING(status OPEN, NON clôturé) —resolutionBranch/evidenceIdrepointés. Ne pas merger sans feu vert d'Avi + re-signature du relecteur.