Skip to content

fix(project): piloter la suppression externe avant d'archiver la ligne - #2816

Open
shikanime wants to merge 5 commits into
mainfrom
fix/archive-locked-retry
Open

shikanime wants to merge 5 commits into
mainfrom
fix/archive-locked-retry

Conversation

@shikanime

@shikanime shikanime commented Oct 5, 2026 •

Copy link
Copy Markdown
Member

Pourquoi

Un projet dont la suppression externe échouait restait définitivement verrouillé en <slug>_<horodatage>_archived : l'archivage commettait le renommage et le verrouillage avant d'émettre project.delete, et toutes les voies de récupération (update, rejeu de hooks, second DELETE) sont fermées par des gardes pour cet état. La seule issue était un UPDATE manuel en base.

Quoi

Inversion de l'ordre dans ProjectService.archive : l'événement project.delete est émis avant toute écriture, le renommage et le verrouillage ne sont posés qu'après un nettoyage externe réussi.

  • Émission sur le projet intact : les listeners nettoient sous le slug d'origine et voient encore dépôts et environnements.
  • Résultat KO d'un listener : 422 Echec des services à la suppression du projet, statut failed, ligne inchangée — un nouveau DELETE rejoue le nettoyage (les listeners sont idempotents, modèle ensure-not-exists).
  • Contrat : 422 ajouté aux réponses de archiveProject.
  • Specs de service mises à jour, dont un cas d'échec de listener vérifiant l'absence d'écriture.

Références

Related: #2813
#2813

@shikanime shikanime self-assigned this Oct 5, 2026
@shikanime shikanime added the bug Something isn't working label Oct 5, 2026
@shikanime shikanime added this to the 9.28.0 milestone Oct 5, 2026
@github-actions github-actions Bot added the built label Oct 5, 2026
@shikanime
shikanime marked this pull request as ready for review October 5, 2026 13:37
@shikanime
shikanime requested a review from a team as a code owner October 5, 2026 13:37
StephaneTrebel
StephaneTrebel previously approved these changes Oct 6, 2026

@StephaneTrebel StephaneTrebel left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Le nettoyage externe est désormais exécuté sur le projet intact et un résultat KO renvoie 422 sans lancer la transaction d’archivage. Le contrat, le test de non-écriture et les contrôles CI sont cohérents avec le ledger #2813.

@shikanime shikanime left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Réordonnancement correct et bien motivé : émettre project.delete sur le projet intact avant toute écriture rend la suppression rejouable (statut failed, ligne ni renommée ni verrouillée), et les specs accompagnent le changement sans perte de couverture — nouveau cas KO inclus, contrat 422 ajouté. Deux nits ci-dessous : le spec de succès ne verrouille pas l'ordre émission-avant-écriture (l'invariant central de la PR) et le return silencieux en cas d'archivage concurrent mérite une trace.

À noter aussi, hors diff : le commentaire de updateProjectStatus dans app-events.service.ts (« A successful project.delete leaves the archived status set when the project was archived ») est devenu obsolète avec le nouvel ordre — au moment de l'émission, la ligne n'est plus jamais archived.

Comment thread apps/server-nestjs/src/modules/project/project.service.spec.ts
Comment thread apps/server-nestjs/src/modules/project/project.service.ts Outdated
@shikanime
shikanime enabled auto-merge October 8, 2026 08:09
@shikanime shikanime added the preview Deploy preview app with Argo-cd label Oct 8, 2026
@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

🤖 Hey !

A preview of the application is available at : https://console-pr-2816.dso.cpin-hp.numerique-interieur.fr

Please be patient, deployment may take a few minutes.

@shikanime

Copy link
Copy Markdown
Member Author

Stress-test sur la preview (delete -> F5, deux DELETE concurrents) : le contrat a tenu, deux défauts périphériques trouvés et corrigés dans c979cfe.

  • Le JSON unknown_error remonté est un KO capturé (persisté dans le log admin par capturePluginResult), pas une réponse 500 : côté nginx les deux DELETE sont sortis en 499 (client parti avant réponse) pendant que le backend terminait, et la voie perdante a bien répondu 422 sans réécriture.
  • Défaut 1 : Keycloak 26.7 répond 500 unknown_error au lieu de 404 quand la suppression en concurrence une autre. deleteGroup tolère désormais le 500 si le groupe n'existe plus au re-fetch.
  • Défaut 2 : le KO obsolète de la requête perdante écrasait status=archived par failed (ligne …_archived verrouillée mais failed en base, d'où la fausse impression de régression). updateProjectStatus n'écrase plus archived.

Unitaires : 28/28 sur keycloak-client.service.spec + app-events.service.spec.

Related: #2813

@StephaneTrebel StephaneTrebel left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Verdict : changements demandés. L’échec de nettoyage laisse désormais le projet rejouable, les deux défauts du stress-test concurrent ont des correctifs ciblés, et la CI est verte. Il reste toutefois une course entre ce nettoyage externe et les mutations du projet : une modification peut encore réconcilier des ressources après leur suppression ; en outre, la branche est à 7 commits derrière main et doit être rebasée avant validation.

Comment thread apps/server-nestjs/src/modules/project/project.service.ts
@shikanime
shikanime force-pushed the fix/archive-locked-retry branch 3 times, most recently from 20d5e83 to d6ae40c Compare October 8, 2026 10:26
@shikanime
shikanime enabled auto-merge October 8, 2026 11:08
@shikanime
shikanime force-pushed the fix/archive-locked-retry branch from d6ae40c to 50a6b3d Compare October 8, 2026 12:25
shikanime and others added 4 commits October 8, 2026 15:29
L'archivage committait le renommage _archived et le verrouillage avant
d'émettre project.delete. Un échec d'un listener laissait donc le projet
verrouillé dans un état sans voie de récupération.

L'émission passe avant la transaction : les listeners nettoyent les
ressources sous le slug intact et voient encore les dépôts et
environnements, un KO lève une 422 et laisse la ligne inchangée (statut
failed, projet rejouable par un nouveau DELETE). Le renommage et le
verrouillage ne sont posés qu'après un nettoyage externe réussi.

Related: #2813
Co-authored-by: Automata <automata@shikanime.studio>
Signed-off-by: William Phetsinorath <william.phetsinorath-open@interieur.gouv.fr>
Change-Id: Ic331cffbf0034681ad123ff26fe1159e6a6a6964

Signed-off-by: Shikanime Deva <22115108+shikanime@users.noreply.github.com>
…t archive

Un DELETE rejoue pendant une suppression en cours fait repondre Keycloak
500 unknown_error au lieu de 404 : le 500 est desormais tolere quand le
groupe n'existe plus au re-fetch, et le statut failed pose par un KO
obsolete ne peut plus ecraser un projet deja archive.

Related: #2813
Co-authored-by: Automata <automata@shikanime.studio>
Signed-off-by: William Phetsinorath <william.phetsinorath-open@interieur.gouv.fr>

Signed-off-by: Shikanime Deva <22115108+shikanime@users.noreply.github.com>
Un update concurrent pendant le nettoyage externe reconsiliait des
ressources derriere la suppression : l'archivage compare maintenant
updatedAt au snapshot pre-emit et repond 409 sans ecrire, la requete
reste rejouable. Trace du 204 silencieux sur ligne deja archivee et
assertion d'ordre emission-avant-ecriture dans le spec.

Signed-off-by: William Phetsinorath <william.phetsinorath-open@interieur.gouv.fr>

Signed-off-by: Shikanime Deva <22115108+shikanime@users.noreply.github.com>
Poser `locked` avant l'emission de `project.delete` fence toutes les
routes de mutation (`@RequireProjectLocked(false)` + re-check
in-transaction dans `update`) : aucune reconciliation externe ne peut
plus passer derriere le nettoyage, y compris apres un echec (422) ou un
conflit (409) - le verrou est conserve pour la reprise.

Corrige la revue bloquante de #2816 (course nettoyage externe vs
mutations), met a jour les docstrings obsoletes sur l'ordre
emission/archivage et rebase sur main.

Co-authored-by: Automata <automata@shikanime.studio>
Signed-off-by: William Phetsinorath <william.phetsinorath-open@interieur.gouv.fr>
@shikanime
shikanime force-pushed the fix/archive-locked-retry branch from 50a6b3d to 0094e10 Compare October 8, 2026 13:32
@shikanime

Copy link
Copy Markdown
Member Author

Vingt-hole fermee : locked est desormais pose avant l'emission de project.delete, ce qui serialize le nettoyage externe avec toutes les routes de mutation (@RequireProjectLocked(false) + re-check in-transaction dans update). Le verrou n'est pas relache sur echec (422 KO, 409 snapshot obsolete) : aucune reconciliation ne peut passer derriere le nettoyage entre l'echec et la reprise du DELETE. Rebase sur main effectue (git range-diff tout =, mergeable), docstrings app-events mises a jour (ordre emission/archivage), tests ajoutes : ordre verrou->emit->ecritures, verrou conserve sur 422 et 409. Vitest 36/36, tsc et ESLint ciblés verts.

Nouveau head : 0094e10df6 — @StephaneTrebel pret pour nouvelle revue.

Comment thread apps/server-nestjs/src/modules/project/project.service.ts Outdated

@StephaneTrebel StephaneTrebel left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🔴 L’archivage échoue systématiquement car le contrôle updatedAt inclut la mise à jour du verrou (@updatedAt). Voir le commentaire inline pour le détail et une piste de correction ; merci de corriger avant validation.

Le controle de conflit comparait le projet recharge a un snapshot
anterieur a l'ecriture du verrou ; l'annotation @updatedat de Prisma
avance ce champ sur cette ecriture, donc chaque suppression repondait
409. La comparaison se fait desormais sur la ligne renvoyee par la pose
du verrou, et le spec de succes verrouille ce comportement (le mock du
verrou bump updatedAt) : la comparaison a l'ancienne echoue le spec.

Corrige la revue bloquante de #2816.

Co-authored-by: Automata <automata@shikanime.studio>
Signed-off-by: William Phetsinorath <william.phetsinorath-open@interieur.gouv.fr>
Change-Id: I2d6b0f7bbcc3061a5c980bb3a197c83a6a6a6964
@cloud-pi-native-sonarqube

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working built preview Deploy preview app with Argo-cd

Projects

None yet

Development

Successfully merging this pull request may close these issues.

🐛 [BUG] - Un projet dont la suppression externe échoue reste verrouillé en <slug>_<horodatage>_archived sans voie de récupération

2 participants