diff --git a/packages/api/src/api/rest/perimetre.queries.ts b/packages/api/src/api/rest/perimetre.queries.ts
index a76f150782aaf4f7c9ce49a2f44f23ed4f7a3e79..5e0b3ebeb8706659c7fdcf6ad55db3e4a63b1bd7 100644
--- a/packages/api/src/api/rest/perimetre.queries.ts
+++ b/packages/api/src/api/rest/perimetre.queries.ts
@@ -14,7 +14,7 @@ import { foretIdValidator } from 'camino-common/src/static/forets'
 import { sdomZoneIdValidator } from 'camino-common/src/static/sdom'
 import { KM2, M2, km2Validator, m2Validator } from 'camino-common/src/number'
 import { isNullOrUndefined, onlyUnique } from 'camino-common/src/typescript-tools'
-import { ZodUnparseable, zodParseEffect, zodParseEffectCallback } from '../../tools/fp-tools'
+import { zodParseEffect, zodParseEffectTyped, ZodUnparseable } from '../../tools/fp-tools'
 import { CaminoError } from 'camino-common/src/zod-tools'
 import { Effect, pipe } from 'effect'
 import { departementIdValidator, toDepartementId } from 'camino-common/src/static/departement'
@@ -58,14 +58,7 @@ export const convertPoints = <T extends z.ZodTypeAny>(
       coordinates => coordinates.length === geojsonPoints.features.length,
       () => ({ message: convertPointsInvalidNumberOfFeaturesError })
     ),
-    Effect.flatMap((result: [number, number][]) => {
-      const check = zodParseEffect(arrayTuple4326CoordinateValidator, result)
-
-      return Effect.matchEffect(check, {
-        onSuccess: () => Effect.succeed(result),
-        onFailure: error => Effect.fail({ ...error, message: invalidSridError, detail: 'Vérifiez que le géosystème correspond bien à celui du fichier' }),
-      })
-    }),
+    Effect.tap((result: [number, number][]) => zodParseEffectTyped(arrayTuple4326CoordinateValidator, result, invalidSridError, 'Vérifiez que le géosystème correspond bien à celui du fichier')),
     Effect.map(coordinates => {
       return {
         type: 'FeatureCollection',
@@ -87,14 +80,14 @@ const perimetreInvalideError = "Le périmètre n'est pas valide dans le référe
 const conversionGeometrieError = 'Impossible de convertir la géométrie en JSON' as const
 const getGeojsonByGeoSystemeIdValidator = z.object({ geojson: multiPolygonValidator })
 const polygon4326CoordinatesValidator = z.array(z.array(arrayTuple4326CoordinateValidator.min(3)).min(1)).min(1)
-
+const transformationImpossible = 'Impossible de transformer le geojson dans le référentiel donné' as const
 export type GetGeojsonByGeoSystemeIdErrorMessages =
   | EffectDbQueryAndValidateErrors
-  | ZodUnparseable
   | typeof conversionSystemeError
   | typeof perimetreInvalideError
   | typeof conversionGeometrieError
   | typeof invalidSridError
+  | typeof transformationImpossible
 export const getGeojsonByGeoSystemeId = (
   pool: Pool,
   fromGeoSystemeId: GeoSystemeId,
@@ -117,16 +110,7 @@ export const getGeojsonByGeoSystemeId = (
       result => result.length === 1,
       () => ({ message: conversionSystemeError, extra: to4326GeoSystemeId })
     ),
-    Effect.flatMap(result => {
-      const coordinates: [number, number][][][] = result[0].geojson.coordinates
-
-      const check = zodParseEffect(polygon4326CoordinatesValidator, coordinates)
-
-      return Effect.matchEffect(check, {
-        onSuccess: () => Effect.succeed(result),
-        onFailure: error => Effect.fail({ ...error, message: invalidSridError, detail: 'Vérifiez que le géosystème correspond bien à celui du fichier' }),
-      })
-    }),
+    Effect.tap(result => zodParseEffectTyped(polygon4326CoordinatesValidator, result[0].geojson.coordinates, invalidSridError, 'Vérifiez que le géosystème correspond bien à celui du fichier')),
     Effect.map(result => {
       if (fromGeoSystemeId === to4326GeoSystemeId) {
         return geojson
@@ -139,7 +123,7 @@ export const getGeojsonByGeoSystemeId = (
 
       return feature
     }),
-    Effect.flatMap(zodParseEffectCallback(featureMultiPolygonValidator))
+    Effect.flatMap(result => zodParseEffectTyped(featureMultiPolygonValidator, result, transformationImpossible))
   )
 }
 
diff --git a/packages/api/src/api/rest/perimetre.ts b/packages/api/src/api/rest/perimetre.ts
index 8015b9c44813d7fe7a049b8430d39016082e7632..f51a56eda1691ac433755dfe5ada0476cc104af3 100644
--- a/packages/api/src/api/rest/perimetre.ts
+++ b/packages/api/src/api/rest/perimetre.ts
@@ -415,6 +415,7 @@ export const geojsonImport: RestNewPostCall<'/rest/geojson/import/:geoSystemeId'
           'Une erreur inattendue est survenue lors de la récupération des informations geojson en base',
           "Impossible d'exécuter la requête dans la base de données",
           'Les données en base ne correspondent pas à ce qui est attendu',
+          'Impossible de transformer le geojson dans le référentiel donné',
           () => ({ ...caminoError, status: HTTP_STATUS.INTERNAL_SERVER_ERROR })
         ),
         Match.whenOr(
diff --git a/packages/api/src/api/rest/titres.ts b/packages/api/src/api/rest/titres.ts
index 709ef81dbc7e6f614967c647d291a499ce0d0d33..3f31cf708289ffeba701ef26c4ad20d61d3c60be 100644
--- a/packages/api/src/api/rest/titres.ts
+++ b/packages/api/src/api/rest/titres.ts
@@ -155,10 +155,17 @@ export const titresAdministrations =
                 derniereEtape = etapesDerniereDemarche[etapesDerniereDemarche.length - 1]
                 if (isNotNullNorUndefined(machine)) {
                   try {
-                    enAttenteDeAdministration = machine.whoIsBlocking(etapesDerniereDemarche).includes(user.administrationId)
+                    const whoIsBlocking = machine.whoIsBlocking(etapesDerniereDemarche)
+                    if (!whoIsBlocking.valid) {
+                      throw new Error(whoIsBlocking.error)
+                    }
+                    enAttenteDeAdministration = whoIsBlocking.value.includes(user.administrationId)
                     const nextEtapes = machine.possibleNextEtapes(etapesDerniereDemarche, getCurrent())
+                    if (!nextEtapes.valid) {
+                      throw new Error(nextEtapes.error)
+                    }
                     prochainesEtapes.push(
-                      ...nextEtapes
+                      ...nextEtapes.value
                         .map(etape => etape.etapeTypeId)
                         .filter(onlyUnique)
                         .filter(etape => !etapesAMasquer.includes(etape))
diff --git a/packages/api/src/business/rules-demarches/axm/oct.machine.test.ts b/packages/api/src/business/rules-demarches/axm/oct.machine.test.ts
index 616a7a027f54fc3a5c87963f7db8ea64e1475bf0..03fe9462846cdf1f9f609cefb17bac7f7ced3410 100644
--- a/packages/api/src/business/rules-demarches/axm/oct.machine.test.ts
+++ b/packages/api/src/business/rules-demarches/axm/oct.machine.test.ts
@@ -19,7 +19,7 @@ describe('vérifie l’arbre d’octroi d’AXM', () => {
         "ENREGISTRER_DEMANDE (confidentielle, déposé                 ) -> [DEMANDER_COMPLEMENTS_POUR_RECEVABILITE,FAIRE_CLASSEMENT_SANS_SUITE,FAIRE_DESISTEMENT_DEMANDEUR,FAIRE_RECEVABILITE_DEMANDE_DEFAVORABLE,FAIRE_RECEVABILITE_DEMANDE_FAVORABLE,RENDRE_DECISION_IMPLICITE_REJET]",
       ]
     `)
-    expect(axmOctMachine.whoIsBlocking(etapes)).toStrictEqual([ADMINISTRATION_IDS['DGTM - GUYANE']])
+    expect(axmOctMachine.whoIsBlocking(etapes)).toStrictEqual({ valid: true, value: [ADMINISTRATION_IDS['DGTM - GUYANE']] })
   })
 
   test('peut faire l’avis du DREAL sans aucun autre avis 30 jours après la saisine des services', () => {
@@ -42,9 +42,13 @@ describe('vérifie l’arbre d’octroi d’AXM', () => {
         "RENDRE_AVIS_DREAL                                     (publique      , en instruction         ) -> [FAIRE_CLASSEMENT_SANS_SUITE,FAIRE_DESISTEMENT_DEMANDEUR,FAIRE_SAISINE_COMMISSION_DEPARTEMENTALE_DES_MINES,RENDRE_AVIS_COMMISSION_DEPARTEMENTALE_DES_MINES,RENDRE_AVIS_COMMISSION_DEPARTEMENTALE_DES_MINES_AJOURNE]",
       ]
     `)
+    const possibleNextEtapes = machine.possibleNextEtapes(etapes, toCaminoDate('2022-06-15'))
+    expect(possibleNextEtapes.valid).toBe(true)
+    if (!possibleNextEtapes.valid) {
+      throw new Error(possibleNextEtapes.error)
+    }
     expect(
-      machine
-        .possibleNextEtapes(etapes, toCaminoDate('2022-06-15'))
+      possibleNextEtapes.value
         .map(({ type }) => type)
         .filter(onlyUnique)
         .toSorted()
@@ -134,7 +138,7 @@ describe('vérifie l’arbre d’octroi d’AXM', () => {
         "FAIRE_CLASSEMENT_SANS_SUITE (confidentielle, classé sans suite      ) -> []",
       ]
     `)
-    expect(machine.whoIsBlocking(etapes)).toStrictEqual([])
+    expect(machine.whoIsBlocking(etapes)).toStrictEqual({ valid: true, value: [] })
   })
 
   test('ne peut pas faire deux fois la même étape à la même date', () => {
@@ -204,9 +208,13 @@ describe('vérifie l’arbre d’octroi d’AXM', () => {
       ETES.avisDesCollectivites.FAIT,
       ETES.avisDesServicesEtCommissionsConsultatives.FAIT,
     ])
+    let possibleNextEtapes = machine.possibleNextEtapes(etapes, toCaminoDate('2022-05-06'))
+    expect(possibleNextEtapes.valid).toBe(true)
+    if (!possibleNextEtapes.valid) {
+      throw new Error(possibleNextEtapes.error)
+    }
     expect(
-      machine
-        .possibleNextEtapes(etapes, toCaminoDate('2022-05-06'))
+      possibleNextEtapes.value
         .map(({ type }) => type)
         .filter(onlyUnique)
         .toSorted()
@@ -218,9 +226,14 @@ describe('vérifie l’arbre d’octroi d’AXM', () => {
         "RENDRE_AVIS_DREAL",
       ]
     `)
+
+    possibleNextEtapes = machine.possibleNextEtapes(etapes, toCaminoDate('2022-05-05'))
+    expect(possibleNextEtapes.valid).toBe(true)
+    if (!possibleNextEtapes.valid) {
+      throw new Error(possibleNextEtapes.error)
+    }
     expect(
-      machine
-        .possibleNextEtapes(etapes, toCaminoDate('2022-05-05'))
+      possibleNextEtapes.value
         .map(({ type }) => type)
         .filter(onlyUnique)
         .toSorted()
diff --git a/packages/api/src/business/rules-demarches/machine-helper.test.ts b/packages/api/src/business/rules-demarches/machine-helper.test.ts
index 671000eda3634ff2ee95744c2b299a77306e025c..da8b1a570b4ecda0eaae9e31fe9e4cee79f2d5ec 100644
--- a/packages/api/src/business/rules-demarches/machine-helper.test.ts
+++ b/packages/api/src/business/rules-demarches/machine-helper.test.ts
@@ -364,7 +364,7 @@ describe('whoIsBlocking', () => {
           date: toCaminoDate('2021-02-03'),
         },
       ])
-    ).toStrictEqual([ADMINISTRATION_IDS['DGTM - GUYANE']])
+    ).toStrictEqual({ valid: true, value: [ADMINISTRATION_IDS['DGTM - GUYANE']] })
   })
 
   test('on attend la DGTM pour la validation du paiement des frais de dossier', () => {
@@ -391,7 +391,7 @@ describe('whoIsBlocking', () => {
           date: toCaminoDate('2021-02-04'),
         },
       ])
-    ).toStrictEqual([ADMINISTRATION_IDS['DGTM - GUYANE']])
+    ).toStrictEqual({ valid: true, value: [ADMINISTRATION_IDS['DGTM - GUYANE']] })
   })
 
   test('on attend personne', () => {
@@ -428,7 +428,7 @@ describe('whoIsBlocking', () => {
           date: toCaminoDate('2021-02-06'),
         },
       ])
-    ).toStrictEqual([])
+    ).toStrictEqual({ valid: true, value: [] })
   })
 })
 
@@ -451,36 +451,39 @@ describe('mainStep', () => {
         toCaminoDate('2021-02-03')
       )
     ).toMatchInlineSnapshot(`
-      [
-        {
-          "contenu": undefined,
-          "etapeStatutId": "fai",
-          "etapeTypeId": "css",
-          "mainStep": false,
-          "type": "CLASSER_SANS_SUITE",
-        },
-        {
-          "contenu": undefined,
-          "etapeStatutId": "fai",
-          "etapeTypeId": "des",
-          "mainStep": false,
-          "type": "DESISTER_PAR_LE_DEMANDEUR",
-        },
-        {
-          "contenu": undefined,
-          "etapeStatutId": "fai",
-          "etapeTypeId": "mod",
-          "mainStep": false,
-          "type": "MODIFIER_DEMANDE",
-        },
-        {
-          "contenu": undefined,
-          "etapeStatutId": "fai",
-          "etapeTypeId": "pfd",
-          "mainStep": true,
-          "type": "PAYER_FRAIS_DE_DOSSIER",
-        },
-      ]
+      {
+        "valid": true,
+        "value": [
+          {
+            "contenu": undefined,
+            "etapeStatutId": "fai",
+            "etapeTypeId": "css",
+            "mainStep": false,
+            "type": "CLASSER_SANS_SUITE",
+          },
+          {
+            "contenu": undefined,
+            "etapeStatutId": "fai",
+            "etapeTypeId": "des",
+            "mainStep": false,
+            "type": "DESISTER_PAR_LE_DEMANDEUR",
+          },
+          {
+            "contenu": undefined,
+            "etapeStatutId": "fai",
+            "etapeTypeId": "mod",
+            "mainStep": false,
+            "type": "MODIFIER_DEMANDE",
+          },
+          {
+            "contenu": undefined,
+            "etapeStatutId": "fai",
+            "etapeTypeId": "pfd",
+            "mainStep": true,
+            "type": "PAYER_FRAIS_DE_DOSSIER",
+          },
+        ],
+      }
     `)
   })
   test('possibleNextEtapes après une recevabilité favorable on rendre un avis des services et commissions consultatives', () => {
@@ -497,50 +500,53 @@ describe('mainStep', () => {
         toCaminoDate('2021-02-03')
       )
     ).toMatchInlineSnapshot(`
-      [
-        {
-          "contenu": undefined,
-          "etapeStatutId": "fai",
-          "etapeTypeId": "asc",
-          "mainStep": true,
-          "type": "RENDRE_AVIS_DES_SERVICES_ET_COMMISSIONS_CONSULTATIVES",
-        },
-        {
-          "contenu": undefined,
-          "etapeStatutId": "fai",
-          "etapeTypeId": "css",
-          "mainStep": false,
-          "type": "CLASSER_SANS_SUITE",
-        },
-        {
-          "contenu": undefined,
-          "etapeStatutId": "fai",
-          "etapeTypeId": "des",
-          "mainStep": false,
-          "type": "DESISTER_PAR_LE_DEMANDEUR",
-        },
-        {
-          "contenu": undefined,
-          "etapeStatutId": "fav",
-          "etapeTypeId": "exp",
-          "mainStep": false,
-          "type": "RECEVOIR_EXPERTISE",
-        },
-        {
-          "contenu": undefined,
-          "etapeStatutId": "def",
-          "etapeTypeId": "exp",
-          "mainStep": false,
-          "type": "RECEVOIR_EXPERTISE",
-        },
-        {
-          "contenu": undefined,
-          "etapeStatutId": "fai",
-          "etapeTypeId": "mod",
-          "mainStep": false,
-          "type": "MODIFIER_DEMANDE",
-        },
-      ]
+      {
+        "valid": true,
+        "value": [
+          {
+            "contenu": undefined,
+            "etapeStatutId": "fai",
+            "etapeTypeId": "asc",
+            "mainStep": true,
+            "type": "RENDRE_AVIS_DES_SERVICES_ET_COMMISSIONS_CONSULTATIVES",
+          },
+          {
+            "contenu": undefined,
+            "etapeStatutId": "fai",
+            "etapeTypeId": "css",
+            "mainStep": false,
+            "type": "CLASSER_SANS_SUITE",
+          },
+          {
+            "contenu": undefined,
+            "etapeStatutId": "fai",
+            "etapeTypeId": "des",
+            "mainStep": false,
+            "type": "DESISTER_PAR_LE_DEMANDEUR",
+          },
+          {
+            "contenu": undefined,
+            "etapeStatutId": "fav",
+            "etapeTypeId": "exp",
+            "mainStep": false,
+            "type": "RECEVOIR_EXPERTISE",
+          },
+          {
+            "contenu": undefined,
+            "etapeStatutId": "def",
+            "etapeTypeId": "exp",
+            "mainStep": false,
+            "type": "RECEVOIR_EXPERTISE",
+          },
+          {
+            "contenu": undefined,
+            "etapeStatutId": "fai",
+            "etapeTypeId": "mod",
+            "mainStep": false,
+            "type": "MODIFIER_DEMANDE",
+          },
+        ],
+      }
     `)
   })
   test('après un avis des services et commissions consultatives on doit avoir la saisine de la commission des autorisations de recherches minières', () => {
@@ -558,50 +564,53 @@ describe('mainStep', () => {
         toCaminoDate('2021-02-03')
       )
     ).toMatchInlineSnapshot(`
-      [
-        {
-          "contenu": undefined,
-          "etapeStatutId": "fai",
-          "etapeTypeId": "css",
-          "mainStep": false,
-          "type": "CLASSER_SANS_SUITE",
-        },
-        {
-          "contenu": undefined,
-          "etapeStatutId": "fai",
-          "etapeTypeId": "des",
-          "mainStep": false,
-          "type": "DESISTER_PAR_LE_DEMANDEUR",
-        },
-        {
-          "contenu": undefined,
-          "etapeStatutId": "fav",
-          "etapeTypeId": "exp",
-          "mainStep": false,
-          "type": "RECEVOIR_EXPERTISE",
-        },
-        {
-          "contenu": undefined,
-          "etapeStatutId": "def",
-          "etapeTypeId": "exp",
-          "mainStep": false,
-          "type": "RECEVOIR_EXPERTISE",
-        },
-        {
-          "contenu": undefined,
-          "etapeStatutId": "fai",
-          "etapeTypeId": "mod",
-          "mainStep": false,
-          "type": "MODIFIER_DEMANDE",
-        },
-        {
-          "contenu": undefined,
-          "etapeStatutId": "fai",
-          "etapeTypeId": "sca",
-          "mainStep": true,
-          "type": "FAIRE_SAISINE_CARM",
-        },
-      ]
+      {
+        "valid": true,
+        "value": [
+          {
+            "contenu": undefined,
+            "etapeStatutId": "fai",
+            "etapeTypeId": "css",
+            "mainStep": false,
+            "type": "CLASSER_SANS_SUITE",
+          },
+          {
+            "contenu": undefined,
+            "etapeStatutId": "fai",
+            "etapeTypeId": "des",
+            "mainStep": false,
+            "type": "DESISTER_PAR_LE_DEMANDEUR",
+          },
+          {
+            "contenu": undefined,
+            "etapeStatutId": "fav",
+            "etapeTypeId": "exp",
+            "mainStep": false,
+            "type": "RECEVOIR_EXPERTISE",
+          },
+          {
+            "contenu": undefined,
+            "etapeStatutId": "def",
+            "etapeTypeId": "exp",
+            "mainStep": false,
+            "type": "RECEVOIR_EXPERTISE",
+          },
+          {
+            "contenu": undefined,
+            "etapeStatutId": "fai",
+            "etapeTypeId": "mod",
+            "mainStep": false,
+            "type": "MODIFIER_DEMANDE",
+          },
+          {
+            "contenu": undefined,
+            "etapeStatutId": "fai",
+            "etapeTypeId": "sca",
+            "mainStep": true,
+            "type": "FAIRE_SAISINE_CARM",
+          },
+        ],
+      }
     `)
   })
   test('après la validation de frais de paiement on doit faire une recevabilité', () => {
@@ -617,57 +626,60 @@ describe('mainStep', () => {
         toCaminoDate('2021-02-03')
       )
     ).toMatchInlineSnapshot(`
-      [
-        {
-          "contenu": undefined,
-          "etapeStatutId": "fai",
-          "etapeTypeId": "css",
-          "mainStep": false,
-          "type": "CLASSER_SANS_SUITE",
-        },
-        {
-          "contenu": undefined,
-          "etapeStatutId": "fai",
-          "etapeTypeId": "des",
-          "mainStep": false,
-          "type": "DESISTER_PAR_LE_DEMANDEUR",
-        },
-        {
-          "contenu": undefined,
-          "etapeStatutId": "fai",
-          "etapeTypeId": "mca",
-          "mainStep": false,
-          "type": "DEMANDER_COMPLEMENTS_MCR",
-        },
-        {
-          "contenu": undefined,
-          "etapeStatutId": "def",
-          "etapeTypeId": "mcr",
-          "mainStep": false,
-          "type": "DECLARER_DEMANDE_DEFAVORABLE",
-        },
-        {
-          "contenu": undefined,
-          "etapeStatutId": "fav",
-          "etapeTypeId": "mcr",
-          "mainStep": true,
-          "type": "DECLARER_DEMANDE_FAVORABLE",
-        },
-        {
-          "contenu": undefined,
-          "etapeStatutId": "fai",
-          "etapeTypeId": "mim",
-          "mainStep": false,
-          "type": "DEMANDER_INFORMATION_MCR",
-        },
-        {
-          "contenu": undefined,
-          "etapeStatutId": "fai",
-          "etapeTypeId": "mod",
-          "mainStep": false,
-          "type": "MODIFIER_DEMANDE",
-        },
-      ]
+      {
+        "valid": true,
+        "value": [
+          {
+            "contenu": undefined,
+            "etapeStatutId": "fai",
+            "etapeTypeId": "css",
+            "mainStep": false,
+            "type": "CLASSER_SANS_SUITE",
+          },
+          {
+            "contenu": undefined,
+            "etapeStatutId": "fai",
+            "etapeTypeId": "des",
+            "mainStep": false,
+            "type": "DESISTER_PAR_LE_DEMANDEUR",
+          },
+          {
+            "contenu": undefined,
+            "etapeStatutId": "fai",
+            "etapeTypeId": "mca",
+            "mainStep": false,
+            "type": "DEMANDER_COMPLEMENTS_MCR",
+          },
+          {
+            "contenu": undefined,
+            "etapeStatutId": "def",
+            "etapeTypeId": "mcr",
+            "mainStep": false,
+            "type": "DECLARER_DEMANDE_DEFAVORABLE",
+          },
+          {
+            "contenu": undefined,
+            "etapeStatutId": "fav",
+            "etapeTypeId": "mcr",
+            "mainStep": true,
+            "type": "DECLARER_DEMANDE_FAVORABLE",
+          },
+          {
+            "contenu": undefined,
+            "etapeStatutId": "fai",
+            "etapeTypeId": "mim",
+            "mainStep": false,
+            "type": "DEMANDER_INFORMATION_MCR",
+          },
+          {
+            "contenu": undefined,
+            "etapeStatutId": "fai",
+            "etapeTypeId": "mod",
+            "mainStep": false,
+            "type": "MODIFIER_DEMANDE",
+          },
+        ],
+      }
     `)
   })
   test('après une recevabilité défavorable on doit avoir un avis des services et commissions consultatives', () => {
@@ -684,36 +696,39 @@ describe('mainStep', () => {
         toCaminoDate('2021-02-03')
       )
     ).toMatchInlineSnapshot(`
-      [
-        {
-          "contenu": undefined,
-          "etapeStatutId": "fai",
-          "etapeTypeId": "asc",
-          "mainStep": true,
-          "type": "RENDRE_AVIS_DES_SERVICES_ET_COMMISSIONS_CONSULTATIVES",
-        },
-        {
-          "contenu": undefined,
-          "etapeStatutId": "fai",
-          "etapeTypeId": "css",
-          "mainStep": false,
-          "type": "CLASSER_SANS_SUITE",
-        },
-        {
-          "contenu": undefined,
-          "etapeStatutId": "fai",
-          "etapeTypeId": "des",
-          "mainStep": false,
-          "type": "DESISTER_PAR_LE_DEMANDEUR",
-        },
-        {
-          "contenu": undefined,
-          "etapeStatutId": "fai",
-          "etapeTypeId": "mod",
-          "mainStep": false,
-          "type": "MODIFIER_DEMANDE",
-        },
-      ]
+      {
+        "valid": true,
+        "value": [
+          {
+            "contenu": undefined,
+            "etapeStatutId": "fai",
+            "etapeTypeId": "asc",
+            "mainStep": true,
+            "type": "RENDRE_AVIS_DES_SERVICES_ET_COMMISSIONS_CONSULTATIVES",
+          },
+          {
+            "contenu": undefined,
+            "etapeStatutId": "fai",
+            "etapeTypeId": "css",
+            "mainStep": false,
+            "type": "CLASSER_SANS_SUITE",
+          },
+          {
+            "contenu": undefined,
+            "etapeStatutId": "fai",
+            "etapeTypeId": "des",
+            "mainStep": false,
+            "type": "DESISTER_PAR_LE_DEMANDEUR",
+          },
+          {
+            "contenu": undefined,
+            "etapeStatutId": "fai",
+            "etapeTypeId": "mod",
+            "mainStep": false,
+            "type": "MODIFIER_DEMANDE",
+          },
+        ],
+      }
     `)
   })
 })
diff --git a/packages/api/src/business/rules-demarches/machine-helper.ts b/packages/api/src/business/rules-demarches/machine-helper.ts
index 9142bd5c2ecc73e5d00a122b05e9cdb0e88a8b4d..c4c604acb1890f5700eaac3aa22f87ae8f5a3e2c 100644
--- a/packages/api/src/business/rules-demarches/machine-helper.ts
+++ b/packages/api/src/business/rules-demarches/machine-helper.ts
@@ -186,7 +186,7 @@ export abstract class CaminoMachine<CaminoContext extends CaminoCommonContext, C
   }
 
   private goTo(etapes: readonly Etape[]):
-    | { valid: false; etapeIndex: number }
+    | { valid: false; etapeIndex: number; error: string }
     | {
         valid: true
         state: CaminoState<CaminoContext, CaminoEvent>
@@ -208,7 +208,7 @@ export abstract class CaminoMachine<CaminoContext extends CaminoCommonContext, C
         if (!service.getSnapshot().can(event) || service.getSnapshot().status === 'done') {
           service.stop()
 
-          return { valid: false, etapeIndex: i }
+          return { valid: false, etapeIndex: i, error: `Les étapes '${JSON.stringify(etapes)}' sont invalides à partir de l’étape ${i}` }
         }
         service.send(event)
       }
@@ -286,21 +286,20 @@ export abstract class CaminoMachine<CaminoContext extends CaminoCommonContext, C
     }
   }
 
-  private assertGoTo(etapes: readonly Etape[]): CaminoState<CaminoContext, CaminoEvent> {
-    const value = this.goTo(etapes)
-    if (!value.valid) {
-      throw new Error(`Les étapes '${JSON.stringify(etapes)}' sont invalides à partir de l’étape ${value.etapeIndex}`)
-    } else {
-      return value.state
+  public whoIsBlocking(etapes: readonly Etape[]):
+    | { valid: false; etapeIndex: number; error: string }
+    | {
+        valid: true
+        value: Intervenant[]
+      } {
+    const state = this.goTo(etapes)
+    if (!state.valid) {
+      return state
     }
-  }
-
-  public whoIsBlocking(etapes: readonly Etape[]): Intervenant[] {
-    const state = this.assertGoTo(etapes)
 
-    const responsables: string[] = [...state.tags]
+    const responsables: string[] = [...state.state.tags]
 
-    return intervenants.filter(r => responsables.includes(tags.responsable[r]))
+    return { valid: true, value: intervenants.filter(r => responsables.includes(tags.responsable[r])) }
   }
 
   // visibleForTesting
@@ -318,17 +317,29 @@ export abstract class CaminoMachine<CaminoContext extends CaminoCommonContext, C
       .toSorted((a, b) => a.type.localeCompare(b.type))
   }
 
-  public possibleNextEtapes(etapes: readonly Etape[], date: CaminoDate): (OmitDistributive<Etape, 'date' | 'titreTypeId' | 'demarcheTypeId'> & { mainStep: boolean; type: CaminoEvent['type'] })[] {
-    const state = this.assertGoTo(etapes)
+  public possibleNextEtapes(
+    etapes: readonly Etape[],
+    date: CaminoDate
+  ):
+    | { valid: false; etapeIndex: number; error: string }
+    | { valid: true; value: (OmitDistributive<Etape, 'date' | 'titreTypeId' | 'demarcheTypeId'> & { mainStep: boolean; type: CaminoEvent['type'] })[] } {
+    const state = this.goTo(etapes)
+
+    if (!state.valid) {
+      return state
+    }
 
     if (isNotNullNorUndefined(state)) {
-      return this.possibleNextEvents(state, date)
-        .flatMap(this.caminoXStateEventToEtapes.bind(this))
-        .filter(isNotNullNorUndefined)
-        .toSorted((a, b) => a.etapeTypeId.localeCompare(b.etapeTypeId))
+      return {
+        valid: true,
+        value: this.possibleNextEvents(state.state, date)
+          .flatMap(this.caminoXStateEventToEtapes.bind(this))
+          .filter(isNotNullNorUndefined)
+          .toSorted((a, b) => a.etapeTypeId.localeCompare(b.etapeTypeId)),
+      }
     }
 
-    return []
+    return { valid: true, value: [] }
   }
 }
 
diff --git a/packages/api/src/business/rules-demarches/procedure-specifique/procedure-specifique.machine.test.ts b/packages/api/src/business/rules-demarches/procedure-specifique/procedure-specifique.machine.test.ts
index cfa13d081b73d345b2f1955ac2fdf23d79e6328e..9ef75833732dda6f86381d0888d42c50b1e4cfa2 100644
--- a/packages/api/src/business/rules-demarches/procedure-specifique/procedure-specifique.machine.test.ts
+++ b/packages/api/src/business/rules-demarches/procedure-specifique/procedure-specifique.machine.test.ts
@@ -676,7 +676,7 @@ describe('vérifie l’arbre des procédures spécifique', () => {
       ETES.decisionDeLAutoriteAdministrative.REJETE_DECISION_IMPLICITE,
     ])
 
-    expect(machine.possibleNextEtapes(etapes, dateFin)).toStrictEqual([])
+    expect(machine.possibleNextEtapes(etapes, dateFin)).toStrictEqual({ valid: true, value: [] })
     expect(machine.demarcheStatut(etapes)).toMatchInlineSnapshot(`
        {
          "demarcheDateDebut": {
@@ -710,7 +710,7 @@ describe('vérifie l’arbre des procédures spécifique', () => {
       ETES.decisionDeLAutoriteAdministrative.REJETE_DECISION_IMPLICITE,
     ])
 
-    expect(machine.possibleNextEtapes(etapes, dateFin)).toStrictEqual([])
+    expect(machine.possibleNextEtapes(etapes, dateFin)).toStrictEqual({ valid: true, value: [] })
     expect(machine.demarcheStatut(etapes)).toMatchInlineSnapshot(`
       {
         "demarcheDateDebut": {
diff --git a/packages/api/src/business/validations/titre-demarche-etat-validate.test.ts b/packages/api/src/business/validations/titre-demarche-etat-validate.test.ts
index 16e21079bfc5e49926cebe204e6d4b0d38fd0fbe..5d4d48f48bead60b7d1ab1db0ba16691fdaa69c0 100644
--- a/packages/api/src/business/validations/titre-demarche-etat-validate.test.ts
+++ b/packages/api/src/business/validations/titre-demarche-etat-validate.test.ts
@@ -806,6 +806,230 @@ describe('getPossiblesEtapesTypes', () => {
     `)
   })
 
+  test("peut déplacer la saisineDuPrefet qui est le même jour que d'autres", () => {
+    const sppId = newEtapeId('spp')
+    const etapes: TitreEtapeForMachine[] = [
+      {
+        typeId: ETAPES_TYPES.demande,
+        date: toCaminoDate('2023-02-28'),
+        isBrouillon: ETAPE_IS_NOT_BROUILLON,
+        id: newEtapeId('demandeId'),
+        ordre: 1,
+        statutId: 'fai',
+        communes: [{ id: toCommuneId('64012') }],
+        demarcheIdsConsentement: [],
+      },
+      {
+        typeId: ETAPES_TYPES.avisDeMiseEnConcurrenceAuJORF,
+        date: toCaminoDate('2023-05-03'),
+        isBrouillon: ETAPE_IS_NOT_BROUILLON,
+        id: newEtapeId('anf'),
+        ordre: 5,
+        statutId: 'ter',
+        communes: [{ id: toCommuneId('64012') }],
+        demarcheIdsConsentement: [],
+      },
+      {
+        typeId: ETAPES_TYPES.enregistrementDeLaDemande,
+        date: toCaminoDate('2023-05-03'),
+        isBrouillon: ETAPE_IS_NOT_BROUILLON,
+        id: newEtapeId('men'),
+        ordre: 2,
+        statutId: 'fai',
+        communes: [],
+        demarcheIdsConsentement: [],
+      },
+      {
+        typeId: ETAPES_TYPES.saisineDuPrefet,
+        date: toCaminoDate('2023-05-03'),
+        isBrouillon: ETAPE_IS_NOT_BROUILLON,
+        id: sppId,
+        ordre: 3,
+        statutId: 'fai',
+        communes: [],
+        demarcheIdsConsentement: [],
+      },
+      {
+        typeId: ETAPES_TYPES.recevabiliteDeLaDemande,
+        date: toCaminoDate('2023-05-03'),
+        isBrouillon: ETAPE_IS_NOT_BROUILLON,
+        id: newEtapeId('mcr'),
+        ordre: 4,
+        statutId: 'fav',
+        communes: [],
+        demarcheIdsConsentement: [],
+      },
+    ]
+
+    expect(
+      getPossiblesEtapesTypes(
+        new PrmOctMachine(TITRES_TYPES_IDS.PERMIS_EXCLUSIF_DE_RECHERCHES_METAUX, DEMARCHES_TYPES_IDS.Octroi),
+        TITRES_TYPES_IDS.PERMIS_EXCLUSIF_DE_RECHERCHES_METAUX,
+        DEMARCHES_TYPES_IDS.Octroi,
+        ETAPES_TYPES.saisineDuPrefet,
+        sppId,
+        toCaminoDate('2023-05-03'),
+        etapes
+      )
+    ).toMatchInlineSnapshot(`
+      {
+        "spp": {
+          "etapeStatutIds": [
+            "fai",
+          ],
+          "mainStep": true,
+        },
+      }
+    `)
+  })
+
+  test("peut déplacer l'enregistrement de la demande le même jour", () => {
+    const menId = newEtapeId('menId')
+    const etapes: TitreEtapeForMachine[] = [
+      {
+        typeId: ETAPES_TYPES.demande,
+        date: toCaminoDate('2023-02-28'),
+        isBrouillon: ETAPE_IS_NOT_BROUILLON,
+        id: newEtapeId('demandeId'),
+        ordre: 1,
+        statutId: 'fai',
+        communes: [{ id: toCommuneId('64012') }],
+        demarcheIdsConsentement: [],
+      },
+      {
+        typeId: ETAPES_TYPES.avisDeMiseEnConcurrenceAuJORF,
+        date: toCaminoDate('2023-05-03'),
+        isBrouillon: ETAPE_IS_NOT_BROUILLON,
+        id: newEtapeId('anf'),
+        ordre: 5,
+        statutId: 'ter',
+        communes: [{ id: toCommuneId('64012') }],
+        demarcheIdsConsentement: [],
+      },
+      {
+        typeId: ETAPES_TYPES.enregistrementDeLaDemande,
+        date: toCaminoDate('2023-05-03'),
+        isBrouillon: ETAPE_IS_NOT_BROUILLON,
+        id: menId,
+        ordre: 2,
+        statutId: 'fai',
+        communes: [],
+        demarcheIdsConsentement: [],
+      },
+      {
+        typeId: ETAPES_TYPES.saisineDuPrefet,
+        date: toCaminoDate('2023-05-03'),
+        isBrouillon: ETAPE_IS_NOT_BROUILLON,
+        id: newEtapeId('spp'),
+        ordre: 3,
+        statutId: 'fai',
+        communes: [],
+        demarcheIdsConsentement: [],
+      },
+      {
+        typeId: ETAPES_TYPES.recevabiliteDeLaDemande,
+        date: toCaminoDate('2023-05-03'),
+        isBrouillon: ETAPE_IS_NOT_BROUILLON,
+        id: newEtapeId('mcr'),
+        ordre: 4,
+        statutId: 'fav',
+        communes: [],
+        demarcheIdsConsentement: [],
+      },
+    ]
+
+    expect(
+      getPossiblesEtapesTypes(
+        new PrmOctMachine(TITRES_TYPES_IDS.PERMIS_EXCLUSIF_DE_RECHERCHES_METAUX, DEMARCHES_TYPES_IDS.Octroi),
+        TITRES_TYPES_IDS.PERMIS_EXCLUSIF_DE_RECHERCHES_METAUX,
+        DEMARCHES_TYPES_IDS.Octroi,
+        ETAPES_TYPES.enregistrementDeLaDemande,
+        menId,
+        toCaminoDate('2023-05-03'),
+        etapes
+      )
+    ).toMatchInlineSnapshot(`
+      {
+        "men": {
+          "etapeStatutIds": [
+            "fai",
+          ],
+          "mainStep": true,
+        },
+      }
+    `)
+  })
+
+  test("ne peut pas déplacer l'enregistrement de la demande après les autres étapes", () => {
+    const menId = newEtapeId('menId')
+    const etapes: TitreEtapeForMachine[] = [
+      {
+        typeId: ETAPES_TYPES.demande,
+        date: toCaminoDate('2023-02-28'),
+        isBrouillon: ETAPE_IS_NOT_BROUILLON,
+        id: newEtapeId('demandeId'),
+        ordre: 1,
+        statutId: 'fai',
+        communes: [{ id: toCommuneId('64012') }],
+        demarcheIdsConsentement: [],
+      },
+      {
+        typeId: ETAPES_TYPES.avisDeMiseEnConcurrenceAuJORF,
+        date: toCaminoDate('2023-05-03'),
+        isBrouillon: ETAPE_IS_NOT_BROUILLON,
+        id: newEtapeId('anf'),
+        ordre: 5,
+        statutId: 'ter',
+        communes: [{ id: toCommuneId('64012') }],
+        demarcheIdsConsentement: [],
+      },
+      {
+        typeId: ETAPES_TYPES.enregistrementDeLaDemande,
+        date: toCaminoDate('2023-05-03'),
+        isBrouillon: ETAPE_IS_NOT_BROUILLON,
+        id: menId,
+        ordre: 2,
+        statutId: 'fai',
+        communes: [],
+        demarcheIdsConsentement: [],
+      },
+      {
+        typeId: ETAPES_TYPES.saisineDuPrefet,
+        date: toCaminoDate('2023-05-03'),
+        isBrouillon: ETAPE_IS_NOT_BROUILLON,
+        id: newEtapeId('spp'),
+        ordre: 3,
+        statutId: 'fai',
+        communes: [],
+        demarcheIdsConsentement: [],
+      },
+      {
+        typeId: ETAPES_TYPES.recevabiliteDeLaDemande,
+        date: toCaminoDate('2023-05-03'),
+        isBrouillon: ETAPE_IS_NOT_BROUILLON,
+        id: newEtapeId('mcr'),
+        ordre: 4,
+        statutId: 'fav',
+        communes: [],
+        demarcheIdsConsentement: [],
+      },
+    ]
+
+    expect(
+      getPossiblesEtapesTypes(
+        new PrmOctMachine(TITRES_TYPES_IDS.PERMIS_EXCLUSIF_DE_RECHERCHES_METAUX, DEMARCHES_TYPES_IDS.Octroi),
+        TITRES_TYPES_IDS.PERMIS_EXCLUSIF_DE_RECHERCHES_METAUX,
+        DEMARCHES_TYPES_IDS.Octroi,
+        ETAPES_TYPES.enregistrementDeLaDemande,
+        menId,
+        toCaminoDate('2023-06-01'),
+        etapes
+      )
+    ).toMatchInlineSnapshot(`
+      {}
+    `)
+  })
+
   test('peut créer une étape sur une procédure spécifique vide', () => {
     expect(getPossiblesEtapesTypes(new ProcedureSpecifiqueMachine('cxm', 'oct'), 'cxm', 'oct', undefined, undefined, toCaminoDate('4000-02-01'), [])).toMatchInlineSnapshot(`
       {
diff --git a/packages/api/src/business/validations/titre-demarche-etat-validate.ts b/packages/api/src/business/validations/titre-demarche-etat-validate.ts
index df69a3fec9286bb1bb684e6682509d495f90df80..611b6d24b6f5597b1a3f2ad4d57d571bc26186a8 100644
--- a/packages/api/src/business/validations/titre-demarche-etat-validate.ts
+++ b/packages/api/src/business/validations/titre-demarche-etat-validate.ts
@@ -192,25 +192,36 @@ export const getPossiblesEtapesTypes = (
 const etapesTypesPossibleACetteDateOuALaPlaceDeLEtape = (machine: CaminoMachines, etapes: TitreEtapeForMachine[], titreEtapeId: string | null, date: CaminoDate): EtapeTypeEtapeStatutWithMainStep => {
   const sortedEtapes = titreEtapesSortAscByOrdre(etapes).filter(etape => etape.id !== titreEtapeId)
   const etapesAvant: Etape[] = []
+  const etapesPendant: Etape[] = []
+
   const etapesApres: Etape[] = []
 
-  // TODO 2022-07-12: Il faudrait mieux gérer les étapes à la même date que l'étape qu'on veut rajouter
-  // elles ne sont ni avant, ni après, mais potentiellement au milieu de toutes ces étapes
-  // UPDATE 2025-03-31: il n'y en a pas en prod
-  etapesAvant.push(...toMachineEtapes(sortedEtapes.filter(etape => etape.date <= date)))
-  etapesApres.push(...toMachineEtapes(sortedEtapes.slice(etapesAvant.length)))
+  etapesAvant.push(...toMachineEtapes(sortedEtapes.filter(etape => etape.date < date)))
+  etapesPendant.push(...toMachineEtapes(sortedEtapes.filter(etape => etape.date === date)))
+  etapesApres.push(...toMachineEtapes(sortedEtapes.slice(etapesAvant.length + etapesPendant.length)))
 
-  const etapesPossiblesRaw = machine.possibleNextEtapes(etapesAvant, date)
+  if (!machine.isEtapesOk(etapesAvant)) {
+    return {}
+  }
   const etapesPossibles = []
-  for (const et of etapesPossiblesRaw) {
-    const newEtapes = [...etapesAvant]
 
-    const items = { ...et, date }
-    newEtapes.push(items)
-    newEtapes.push(...etapesApres)
+  for (let i = 0; i <= etapesPendant.length; i++) {
+    const etapeEnCours = [...etapesAvant, ...etapesPendant.slice(0, i)]
+    const etapesPossiblesRaw = machine.possibleNextEtapes(etapeEnCours, date)
+    if (etapesPossiblesRaw.valid) {
+      for (const et of etapesPossiblesRaw.value) {
+        const newEtapes = [...etapeEnCours]
+
+        const items = { ...et, date }
+        newEtapes.push(items)
+        newEtapes.push(...etapesPendant.slice(i))
 
-    if (machine.isEtapesOk(newEtapes)) {
-      etapesPossibles.push(et)
+        newEtapes.push(...etapesApres)
+
+        if (machine.isEtapesOk(newEtapes)) {
+          etapesPossibles.push(et)
+        }
+      }
     }
   }
 
diff --git a/packages/api/src/tools/fp-tools.ts b/packages/api/src/tools/fp-tools.ts
index b670998c55db688ec376adf1c39af32f85ab9c74..73202de0231f6a40f61accd5cadbb9af8a25ff8a 100644
--- a/packages/api/src/tools/fp-tools.ts
+++ b/packages/api/src/tools/fp-tools.ts
@@ -1,3 +1,4 @@
+import { isNotNullNorUndefined } from 'camino-common/src/typescript-tools'
 import { CaminoError, CaminoZodErrorReadableMessage, translateIssue } from 'camino-common/src/zod-tools'
 import { Cause, Effect, Exit, pipe } from 'effect'
 import { ZodTypeAny } from 'zod'
@@ -36,10 +37,10 @@ export const zodParseEffect = <T extends ZodTypeAny>(validator: T, item: unknown
   })
 }
 
-export const zodParseEffectTyped = <T extends ZodTypeAny, U extends string>(validator: T, item: T['_output'], errorMessage: U): Effect.Effect<T['_output'], CaminoError<U>> => {
+export const zodParseEffectTyped = <T extends ZodTypeAny, U extends string>(validator: T, item: T['_output'], errorMessage: U, detail?: string): Effect.Effect<T['_output'], CaminoError<U>> => {
   return Effect.try({
     try: () => validator.parse(item),
-    catch: myError => ({ message: errorMessage, detail: zodErrorToDetail(myError), zodErrorReadableMessage: zodErrorToReadableMessage(myError) }),
+    catch: myError => ({ message: errorMessage, detail: isNotNullNorUndefined(detail) ? detail : zodErrorToDetail(myError), zodErrorReadableMessage: zodErrorToReadableMessage(myError) }),
   })
 }