Skip to content

fix(parity): preuve WIF 3 chemins rejouable de bout en bout (P0-A2-09) - #46

Merged
openaxcloud merged 10 commits into
mainfrom
feat/wif-three-paths-replayable
Aug 4, 2026
Merged

fix(parity): preuve WIF 3 chemins rejouable de bout en bout (P0-A2-09)#46
openaxcloud merged 10 commits into
mainfrom
feat/wif-three-paths-replayable

Conversation

@openaxcloud

Copy link
Copy Markdown
Owner

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.sh provisionne 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

# Défaut Correction
1 repro.sh provisionnait puis commentait les 3 chemins Réécrit exécutable end-to-end — plus rien de commenté, tout est joué
2 Workflow GitHub codé en dur sur un projet supprimé wif-proof.yml paramétré par inputs workflow_dispatch (project/number/bucket/SAs/préfixe) → rejouable après teardown
3 Le curl autorisé ne rougissait pas sur 403/404 Chaque lecture autorisée exige HTTP 200 + contenu attendu, sinon échec (Cloud Run, GitHub, GKE)
4 Le négatif GKE n'était qu'une assertion Vrai cluster GKE de test : pod KSA non liée réellement joué, lecture bucket refusée 403 archivée
5 Dockerfile COPY main.py . (fichier = cloudrun-main.py) Corrigé : COPY cloudrun-main.py main.py

⚠️ .gitignore (*.log) avalait les preuves → exception !docs/deploy-evidence/** ajoutée ; les 3 .log de 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/ :

  • Cloud Run — autorisé read_status=200 + contenu (keyUsed:false) / négatif SA sans droit 403.
  • GitHub OIDC — run 29906107467 success : credential external_account (fédéré, pas une clé), READ_HTTP=200 + contenu / négatif impersonation SA non autorisée AUTH_STEP_OUTCOME=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é).
  • Teardown joué : cluster → Cloud Run → AR → provider+pool WIF → 2 SAs → projet DELETE_REQUESTED. 0 projet actif restant (coût ~0 $).

Sécurité

Projet de TEST dédié sous folder ecode-factory-testjamais vibecore-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/evidenceId repointés. Ne pas merger sans feu vert d'Avi + re-signature du relecteur.

Avi and others added 2 commits July 22, 2026 11:32
…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>
Copilot AI review requested due to automatic review settings July 22, 2026 09:12
@github-actions

Copy link
Copy Markdown
Contributor

ℹ️ Preview deployment not configured

Name Info
Latest commit 0ac5521
Status Preview deployment requires Cloudflare secrets

To enable preview deployments, repository maintainers can add:

  • CLOUDFLARE_API_TOKEN secret
  • CLOUDFLARE_ACCOUNT_ID secret

Built with E-Code

@openaxcloud openaxcloud changed the title fix(parity): WIF 3 chemins — preuve REJOUABLE de bout en bout (P0-A2-09) fix(parity): preuve WIF 3 chemins rejouable de bout en bout (P0-A2-09) Jul 22, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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; }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment on lines +128 to +130
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment on lines +183 to +185
[ -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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/... avec repro.sh, un workflow dispatchable wif-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 .gitignore pour 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.

Comment on lines +33 to +38
{
"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
Comment on lines +38 to +45
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
Comment on lines +112 to +118
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..."
Comment on lines +182 to +186
# 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é)"
Comment on lines +24 to +32
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
Comment on lines +54 to +63
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
@github-actions

Copy link
Copy Markdown
Contributor

ℹ️ Preview deployment not configured

Name Info
Latest commit d0680e1
Status Preview deployment requires Cloudflare secrets

To enable preview deployments, repository maintainers can add:

  • CLOUDFLARE_API_TOKEN secret
  • CLOUDFLARE_ACCOUNT_ID secret

Built with E-Code

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>
@github-actions

Copy link
Copy Markdown
Contributor

ℹ️ Preview deployment not configured

Name Info
Latest commit 9cc4a15
Status Preview deployment requires Cloudflare secrets

To enable preview deployments, repository maintainers can add:

  • CLOUDFLARE_API_TOKEN secret
  • CLOUDFLARE_ACCOUNT_ID secret

Built with E-Code

…-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>
@github-actions

Copy link
Copy Markdown
Contributor

ℹ️ Preview deployment not configured

Name Info
Latest commit b1949b6
Status Preview deployment requires Cloudflare secrets

To enable preview deployments, repository maintainers can add:

  • CLOUDFLARE_API_TOKEN secret
  • CLOUDFLARE_ACCOUNT_ID secret

Built with E-Code

Avi and others added 5 commits July 23, 2026 22:32
…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é
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

ℹ️ Preview deployment not configured

Name Info
Latest commit ff104d2
Status Preview deployment requires Cloudflare secrets

To enable preview deployments, repository maintainers can add:

  • CLOUDFLARE_API_TOKEN secret
  • CLOUDFLARE_ACCOUNT_ID secret

Built with E-Code

@openaxcloud
openaxcloud merged commit 1e45bfb into main Aug 4, 2026
19 of 20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants