Repository: ovh/cds
Pull request #5740 (2021-03-04 09:56:44)

fix(cdn): use run-result instead of artifact
1. Description
1. Related issues
1. About tests
1. Mentions

@ovh/cds


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug.png' alt='Bug' width='16' height='16' />](https://sonarcloud.io/project/issues?id=ovh_cds&pullRequest=5740&resolved=false&types=BUG) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=ovh_cds&pullRequest=5740&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=ovh_cds&pullRequest=5740&resolved=false&types=BUG)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability.png' alt='Vulnerability' width='16' height='16' />](https://sonarcloud.io/project/issues?id=ovh_cds&pullRequest=5740&resolved=false&types=VULNERABILITY) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=ovh_cds&pullRequest=5740&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=ovh_cds&pullRequest=5740&resolved=false&types=VULNERABILITY)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot.png' alt='Security Hotspot' width='16' height='16' />](https://sonarcloud.io/project/security_hotspots?id=ovh_cds&pullRequest=5740&resolved=false&types=SECURITY_HOTSPOT) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/security_hotspots?id=ovh_cds&pullRequest=5740&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=ovh_cds&pullRequest=5740&resolved=false&types=SECURITY_HOTSPOT)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell.png' alt='Code Smell' width='16' height='16' />](https://sonarcloud.io/project/issues?id=ovh_cds&pullRequest=5740&resolved=false&types=CODE_SMELL) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=ovh_cds&pullRequest=5740&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=ovh_cds&pullRequest=5740&resolved=false&types=CODE_SMELL)

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/60.png' alt='61.3%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=ovh_cds&pullRequest=5740&metric=new_coverage&view=list) [61.3% Coverage](https://sonarcloud.io/component_measures?id=ovh_cds&pullRequest=5740&metric=new_coverage&view=list)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3.png' alt='0.0%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=ovh_cds&pullRequest=5740&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=ovh_cds&pullRequest=5740&metric=new_duplicated_lines_density&view=list)



Patch:
diff --git a/cli/cdsctl/workflow_artifact.go b/cli/cdsctl/workflow_artifact.go
index 3c836eb03..401f70a22 100644
--- a/cli/cdsctl/workflow_artifact.go
+++ b/cli/cdsctl/workflow_artifact.go
@@ -176,7 +176,7 @@ func workflowArtifactDownloadRun(v cli.Values) error {
 				return err
 			}
 			fmt.Printf("Downloading %s...\n", artifactData.Name)
-			r, err := client.CDNItemDownload(context.Background(), confCDN.HTTPURL, artifactData.CDNRefHash, sdk.CDNTypeItemArtifact)
+			r, err := client.CDNItemDownload(context.Background(), confCDN.HTTPURL, artifactData.CDNRefHash, sdk.CDNTypeItemRunResult)
 			if err != nil {
 				return err
 			}
diff --git a/engine/api/api_routes.go b/engine/api/api_routes.go
index 0f308a810..9a1ee2930 100644
--- a/engine/api/api_routes.go
+++ b/engine/api/api_routes.go
@@ -268,9 +268,9 @@ func (api *API) InitRouter() {
 	r.Handle("/project/{key}/workflows/{permWorkflowName}/runs/{number}/stop", Scope(sdk.AuthConsumerScopeRun), r.POSTEXECUTE(api.stopWorkflowRunHandler, MaintenanceAware()))
 	r.Handle("/project/{key}/workflows/{permWorkflowName}/runs/{number}/vcs/resync", Scope(sdk.AuthConsumerScopeRun), r.POSTEXECUTE(api.postResyncVCSWorkflowRunHandler))
 	r.Handle("/project/{key}/workflows/{permWorkflowName}/runs/{number}/artifacts", Scope(sdk.AuthConsumerScopeRun), r.GET(api.getWorkflowRunArtifactsHandler))
-	r.Handle("/project/{key}/workflows/{permWorkflowName}/runs/{number}/artifacts/check", Scope(sdk.AuthConsumerScopeRunExecution), r.POST(api.workflowRunArtifactCheckUploadHandler))
 	r.Handle("/project/{key}/workflows/{permWorkflowName}/runs/{number}/artifacts/links", Scope(sdk.AuthConsumerScopeRun), r.GET(api.getWorkflowRunArtifactLinksHandler))
 	r.Handle("/project/{key}/workflows/{permWorkflowName}/runs/{number}/results", Scope(sdk.AuthConsumerScopeRunExecution), r.GET(api.getWorkflowRunResultsHandler), r.POST(api.postWorkflowRunResultsHandler))
+	r.Handle("/project/{key}/workflows/{permWorkflowName}/runs/{number}/results/check", Scope(sdk.AuthConsumerScopeRunExecution), r.POST(api.workflowRunResultCheckUploadHandler))
 	r.Handle("/project/{key}/workflows/{permWorkflowName}/runs/{number}/nodes/{nodeRunID}", Scope(sdk.AuthConsumerScopeRun), r.GET(api.getWorkflowNodeRunHandler))
 	r.Handle("/project/{key}/workflows/{permWorkflowName}/runs/{number}/nodes/{nodeRunID}/results", Scope(sdk.AuthConsumerScopeRunExecution), r.GET(api.getWorkflowNodeRunResultsHandler))
 	r.Handle("/project/{key}/workflows/{permWorkflowName}/runs/{number}/nodes/{nodeRunID}/stop", Scope(sdk.AuthConsumerScopeRun), r.POSTEXECUTE(api.stopWorkflowNodeRunHandler, MaintenanceAware()))
diff --git a/engine/api/workflow/workflow_run_results.go b/engine/api/workflow/workflow_run_results.go
index 4026eb6cc..13cd06640 100644
--- a/engine/api/workflow/workflow_run_results.go
+++ b/engine/api/workflow/workflow_run_results.go
@@ -17,13 +17,13 @@ var (
 	KeyResult = cache.Key("run", "result")
 )
 
-func GetArtifactResultKey(runID int64, artifactName string) string {
-	return cache.Key(KeyResult, string(sdk.WorkflowRunResultTypeArtifact), strconv.Itoa(int(runID)), artifactName)
+func GetRunResultKey(runID int64, t sdk.WorkflowRunResultType, fileName string) string {
+	return cache.Key(KeyResult, string(t), strconv.Itoa(int(runID)), fileName)
 }
 
-func CanUploadArtifact(ctx context.Context, db *gorp.DbMap, store cache.Store, wr sdk.WorkflowRun, artifactRef sdk.CDNArtifactAPIRef) (bool, error) {
+func CanUploadRunResult(ctx context.Context, db *gorp.DbMap, store cache.Store, wr sdk.WorkflowRun, apiRef sdk.CDNRunResultAPIRef) (bool, error) {
 	// Check run
-	if wr.ID != artifactRef.RunID {
+	if wr.ID != apiRef.RunID {
 		return false, sdk.WrapError(sdk.ErrInvalidData, "unable to upload and artifact for this run")
 	}
 	if sdk.StatusIsTerminated(wr.Status) {
@@ -36,9 +36,9 @@ func CanUploadArtifact(ctx context.Context, db *gorp.DbMap, store cache.Store, w
 		if len(nodeRuns) < 1 {
 			continue
 		}
-		// get last noderun
+		// Get last noderun
 		nodeRun := nodeRuns[0]
-		if nodeRun.ID != artifactRef.RunNodeID {
+		if nodeRun.ID != apiRef.RunNodeID {
 			continue
 		}
 		nrs = nodeRuns
@@ -47,36 +47,48 @@ func CanUploadArtifact(ctx context.Context, db *gorp.DbMap, store cache.Store, w
 		}
 	}
 	if len(nrs) == 0 {
-		return false, sdk.WrapError(sdk.ErrNotFound, "unable to find node run: %d", artifactRef.RunNodeID)
+		return false, sdk.WrapError(sdk.ErrNotFound, "unable to find node run: %d", apiRef.RunNodeID)
 	}
 
 	// Check job data
-	nodeRunJob, err := LoadNodeJobRun(ctx, db, store, artifactRef.RunJobID)
+	nodeRunJob, err := LoadNodeJobRun(ctx, db, store, apiRef.RunJobID)
 	if err != nil {
 		return false, err
 	}
-	if nodeRunJob.WorkflowNodeRunID != artifactRef.RunNodeID {
-		return false, sdk.WrapError(sdk.ErrInvalidData, "invalid node run %d", artifactRef.RunNodeID)
+	if nodeRunJob.WorkflowNodeRunID != apiRef.RunNodeID {
+		return false, sdk.WrapError(sdk.ErrInvalidData, "invalid node run %d", apiRef.RunNodeID)
 	}
 	if sdk.StatusIsTerminated(nodeRunJob.Status) {
 		return false, sdk.WrapError(sdk.ErrInvalidData, "unable to upload artifact on a terminated job")
 	}
 
-	// Check artifact name
-	runResults, err := LoadRunResultsByRunIDAndType(ctx, db, artifactRef.RunID, sdk.WorkflowRunResultTypeArtifact)
+	// Check File Name
+	runResults, err := LoadRunResultsByRunIDAndType(ctx, db, apiRef.RunID, apiRef.RunResultType)
 	if err != nil {
-		return false, sdk.WrapError(err, "unable to load run results for run %d", artifactRef.RunID)
+		return false, sdk.WrapError(err, "unable to load run results for run %d", apiRef.RunID)
 	}
 	for _, result := range runResults {
-		refArt, err := result.GetArtifact()
-		if err != nil {
-			return false, err
+		var fileName string
+		switch apiRef.RunResultType {
+		case sdk.WorkflowRunResultTypeArtifact:
+			refArt, err := result.GetArtifact()
+			if err != nil {
+				return false, err
+			}
+			fileName = refArt.Name
+		case sdk.WorkflowRunResultTypeCoverage:
+			refCov, err := result.GetCoverage()
+			if err != nil {
+				return false, err
+			}
+			fileName = refCov.Name
 		}
-		if refArt.Name != artifactRef.ToFilename() {
+
+		if fileName != apiRef.ToFilename() {
 			continue
 		}
 
-		// find artifact in node run history
+		// If we find a run result with same check, check subnumber
 		var previousNodeRunUpload *sdk.WorkflowNodeRun
 		for _, nr := range nrs {
 			if nr.ID != result.WorkflowNodeRunID {
@@ -86,15 +98,15 @@ func CanUploadArtifact(ctx context.Context, db *gorp.DbMap, store cache.Store, w
 			break
 		}
 		if previousNodeRunUpload == nil {
-			return false, sdk.WrapError(sdk.ErrConflictData, "artifact %s has already been uploaded from another pipeline", artifactRef.ArtifactName)
+			return false, sdk.WrapError(sdk.ErrConflictData, "artifact %s has already been uploaded from another pipeline", apiRef.ArtifactName)
 		}
 
 		// Check Sub num
 		if result.SubNum == nrs[0].SubNumber {
-			return false, sdk.WrapError(sdk.ErrConflictData, "artifact %s has already been uploaded", artifactRef.ArtifactName)
+			return false, sdk.WrapError(sdk.ErrConflictData, "artifact %s has already been uploaded", apiRef.ArtifactName)
 		}
 		if result.SubNum > nrs[0].SubNumber {
-			return false, sdk.WrapError(sdk.ErrConflictData, "artifact %s cannot be uploaded into a previous run", artifactRef.ArtifactName)
+			return false, sdk.WrapError(sdk.ErrConflictData, "artifact %s cannot be uploaded into a previous run", apiRef.ArtifactName)
 		}
 	}
 	return true, nil
@@ -137,7 +149,7 @@ func verifyAddResultArtifact(store cache.Store, runResult *sdk.WorkflowRunResult
 		return "", err
 	}
 
-	cacheKey := GetArtifactResultKey(runResult.WorkflowRunID, artifactRunResult.Name)
+	cacheKey := GetRunResultKey(runResult.WorkflowRunID, runResult.Type, artifactRunResult.Name)
 	b, err := store.Exist(cacheKey)
 	if err != nil {
 		return cacheKey, err
diff --git a/engine/api/workflow_application.go b/engine/api/workflow_application.go
index 9d7646972..f258856f6 100644
--- a/engine/api/workflow_application.go
+++ b/engine/api/workflow_application.go
@@ -173,7 +173,7 @@ func (api *API) releaseApplicationWorkflowHandler() service.Handler {
 			var lastErr error
 			for {
 				attempt++
-				reader, err := api.Client.CDNItemDownload(ctx, cdnHTTP, r.CDNRefHash, sdk.CDNTypeItemArtifact)
+				reader, err := api.Client.CDNItemDownload(ctx, cdnHTTP, r.CDNRefHash, sdk.CDNTypeItemRunResult)
 				if err != nil {
 					return err
 				}
diff --git a/engine/api/workflow_run.go b/engine/api/workflow_run.go
index 176a93e0a..53f5e9663 100644
--- a/engine/api/workflow_run.go
+++ b/engine/api/workflow_run.go
@@ -1478,7 +1478,7 @@ func (api *API) postWorkflowRunResultsHandler() service.Handler {
 	}
 }
 
-func (api *API) workflowRunArtifactCheckUploadHandler() service.Handler {
+func (api *API) workflowRunResultCheckUploadHandler() service.Handler {
 	return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
 		vars := mux.Vars(r)
 		key := vars["key"]
@@ -1500,13 +1500,13 @@ func (api *API) workflowRunArtifactCheckUploadHandler() service.Handler {
 			return sdk.WrapError(sdk.ErrForbidden, "only CDN can call this route")
 		}
 
-		var apiRef sdk.CDNArtifactAPIRef
+		var apiRef sdk.CDNRunResultAPIRef
 		if err := service.UnmarshalBody(r, &apiRef); err != nil {
 			return sdk.WithStack(err)
 		}
 
 		if apiRef.ToFilename() == "" {
-			return sdk.WrapError(sdk.ErrInvalidData, "unable to read artifact api ref")
+			return sdk.WrapError(sdk.ErrInvalidData, "unable to read run result api ref")
 		}
 
 		wr, err := workflow.LoadRun(ctx, api.mustDB(), key, name, number, workflow.LoadRunOptions{DisableDetailledNodeRun: true})
@@ -1514,7 +1514,7 @@ func (api *API) workflowRunArtifactCheckUploadHandler() service.Handler {
 			return err
 		}
 
-		b, err := workflow.CanUploadArtifact(ctx, api.mustDBWithCtx(ctx), api.Cache, *wr, apiRef)
+		b, err := workflow.CanUploadRunResult(ctx, api.mustDBWithCtx(ctx), api.Cache, *wr, apiRef)
 		if err != nil {
 			return err
 		}
@@ -1523,7 +1523,7 @@ func (api *API) workflowRunArtifactCheckUploadHandler() service.Handler {
 		}
 
 		// Save check
-		if err := api.Cache.SetWithTTL(workflow.GetArtifactResultKey(apiRef.RunID, apiRef.ArtifactName), true, 600); err != nil {
+		if err := api.Cache.SetWithTTL(workflow.GetRunResultKey(apiRef.RunID, apiRef.RunResultType, apiRef.ArtifactName), true, 600); err != nil {
 			return sdk.WrapError(err, "unable to cache result artifact check %s ", apiRef.ToFilename())
 		}
 		return nil
diff --git a/engine/api/workflow_run_artifact.go b/engine/api/workflow_run_artifact.go
index 31d504de5..674be65ca 100644
--- a/engine/api/workflow_run_artifact.go
+++ b/engine/api/workflow_run_artifact.go
@@ -37,7 +37,7 @@ func (api *API) getWorkflowRunArtifactLinksHandler() service.Handler {
 			return err
 		}
 
-		result, err := cdn.ListItems(ctx, api.mustDB(), sdk.CDNTypeItemArtifact, map[string]string{cdn.ParamRunID: strconv.Itoa(int(wr.ID))})
+		result, err := cdn.ListItems(ctx, api.mustDB(), sdk.CDNTypeItemRunResult, map[string]string{cdn.ParamRunID: strconv.Itoa(int(wr.ID))})
 		if err != nil {
 			return err
 		}
diff --git a/engine/api/workflow_run_log.go b/engine/api/workflow_run_log.go
index 33c9dc1ee..66d5b207e 100644
--- a/engine/api/workflow_run_log.go
+++ b/engine/api/workflow_run_log.go
@@ -315,7 +315,7 @@ func (api *API) getWorkflowAccessHandler() service.Handler {
 		switch sdk.CDNItemType(itemType) {
 		case sdk.CDNTypeItemStepLog, sdk.CDNTypeItemServiceLog:
 			enabled = true
-		case sdk.CDNTypeItemArtifact:
+		case sdk.CDNTypeItemRunResult:
 			_, enabled = featureflipping.IsEnabled(ctx, gorpmapping.Mapper, api.mustDB(), sdk.FeatureCDNArtifact, map[string]string{
 				"project_key": projectKey,
 			})
diff --git a/engine/cdn/auth.go b/engine/cdn/auth.go
index e4a700886..39bf93b71 100644
--- a/engine/cdn/auth.go
+++ b/engine/cdn/auth.go
@@ -95,8 +95,8 @@ func (s *Service) itemAccessCheck(ctx context.Context, item sdk.CDNItem) error {
 		logRef, _ := item.GetCDNLogApiRef()
 		projectKey = logRef.ProjectKey
 		workflowName = logRef.WorkflowName
-	case sdk.CDNTypeItemArtifact:
-		artRef, _ := item.GetCDNArtifactApiRef()
+	case sdk.CDNTypeItemRunResult:
+		artRef, _ := item.GetCDNRunResultApiRef()
 		projectKey = artRef.ProjectKey
 		workflowName = artRef.WorkflowName
 	case sdk.CDNTypeItemWorkerCache:
@@ -107,7 +107,7 @@ func (s *Service) itemAccessCheck(ctx context.Context, item sdk.CDNItem) error {
 	}
 
 	switch item.Type {
-	case sdk.CDNTypeItemStepLog, sdk.CDNTypeItemServiceLog, sdk.CDNTypeItemArtifact:
+	case sdk.CDNTypeItemStepLog, sdk.CDNTypeItemServiceLog, sdk.CDNTypeItemRunResult:
 		if err := s.Client.WorkflowAccess(ctx, projectKey, workflowName, sessionID, item.Type); err != nil {
 			return sdk.NewErrorWithStack(err, sdk.ErrNotFound)
 		}
diff --git a/engine/cdn/cdn_file.go b/engine/cdn/cdn_file.go
index 23619dac0..88a013db6 100644
--- a/engine/cdn/cdn_file.go
+++ b/engine/cdn/cdn_file.go
@@ -20,8 +20,8 @@ import (
 func (s *Service) storeFile(ctx context.Context, sig cdn.Signature, reader io.ReadCloser) error {
 	var itemType sdk.CDNItemType
 	switch {
-	case sig.Worker.ArtifactName != "":
-		itemType = sdk.CDNTypeItemArtifact
+	case sig.Worker.FileName != "":
+		itemType = sdk.CDNTypeItemRunResult
 	case sig.Worker.CacheTag != "":
 		itemType = sdk.CDNTypeItemWorkerCache
 	default:
@@ -42,7 +42,7 @@ func (s *Service) storeFile(ctx context.Context, sig cdn.Signature, reader io.Re
 	// Check Item unicity
 	_, err = item.LoadByAPIRefHashAndType(ctx, s.Mapper, s.mustDBWithCtx(ctx), hashRef, itemType)
 	if err == nil {
-		return sdk.WrapError(sdk.ErrConflictData, "cannot upload the same artifact twice")
+		return sdk.WrapError(sdk.ErrConflictData, "cannot upload the same file twice")
 	}
 	if !sdk.ErrorIs(err, sdk.ErrNotFound) {
 		return err
@@ -56,10 +56,10 @@ func (s *Service) storeFile(ctx context.Context, sig cdn.Signature, reader io.Re
 	}
 
 	switch itemType {
-	case sdk.CDNTypeItemArtifact:
-		// CALL CDS API to CHECK IF WE CAN UPLOAD ARTIFACT
-		artiApiRef, _ := it.GetCDNArtifactApiRef()
-		if err := s.Client.WorkflowRunArtifactCheck(ctx, sig.ProjectKey, sig.WorkflowName, sig.RunNumber, *artiApiRef); err != nil {
+	case sdk.CDNTypeItemRunResult:
+		// Call CDS API to check if we can upload the run result
+		runResultApiRef, _ := it.GetCDNRunResultApiRef()
+		if err := s.Client.WorkflowRunResultCheck(ctx, sig.ProjectKey, sig.WorkflowName, sig.RunNumber, *runResultApiRef); err != nil {
 			return err
 		}
 	}
@@ -130,8 +130,8 @@ func (s *Service) storeFile(ctx context.Context, sig cdn.Signature, reader io.Re
 	}
 
 	switch itemType {
-	case sdk.CDNTypeItemArtifact:
-		artiApiRef, _ := it.GetCDNArtifactApiRef()
+	case sdk.CDNTypeItemRunResult:
+		artiApiRef, _ := it.GetCDNRunResultApiRef()
 		// Call CDS to insert workflow run result
 		artiResult := sdk.WorkflowRunResultArtifact{
 			Name:       apiRef.ToFilename(),
diff --git a/engine/cdn/cdn_gc.go b/engine/cdn/cdn_gc.go
index 027372b04..a84b2792b 100644
--- a/engine/cdn/cdn_gc.go
+++ b/engine/cdn/cdn_gc.go
@@ -137,7 +137,13 @@ func (s *Service) cleanItemToDelete(ctx context.Context) error {
 }
 
 func (s *Service) cleanBuffer(ctx context.Context) error {
-	storageCount := int64(len(s.Units.Storages) + 1)
+	storageCount := int64(1)
+	for _, s := range s.Units.Storages {
+		if !s.CanSync() {
+			continue
+		}
+		storageCount++
+	}
 	for _, bu := range s.Units.Buffers {
 		itemIDs, err := storage.LoadAllSynchronizedItemIDs(s.mustDBWithCtx(ctx), bu.ID(), storageCount)
 		if err != nil {
diff --git a/engine/cdn/cdn_item.go b/engine/cdn/cdn_item.go
index fdbf6845d..169211ac4 100644
--- a/engine/cdn/cdn_item.go
+++ b/engine/cdn/cdn_item.go
@@ -85,7 +85,7 @@ func (s *Service) downloadItem(ctx context.Context, t sdk.CDNItemType, apiRefHas
 		if err := s.downloadLog(ctx, t, apiRefHash, w, opts); err != nil {
 			return err
 		}
-	case sdk.CDNTypeItemArtifact, sdk.CDNTypeItemWorkerCache:
+	case sdk.CDNTypeItemRunResult, sdk.CDNTypeItemWorkerCache:
 		if err := s.downloadFile(ctx, t, apiRefHash, w); err != nil {
 			return err
 		}
diff --git a/engine/cdn/cdn_metrics.go b/engine/cdn/cdn_metrics.go
index 5d04d14cb..9ee91be58 100644
--- a/engine/cdn/cdn_metrics.go
+++ b/engine/cdn/cdn_metrics.go
@@ -194,7 +194,7 @@ func (s *Service) ComputeMetrics(ctx context.Context) {
 }
 
 func (s *Service) countItemsForUnit(ctx context.Context, storageUnit storage.Interface) []storage.Stat {
-	types := []sdk.CDNItemType{sdk.CDNTypeItemStepLog, sdk.CDNTypeItemServiceLog, sdk.CDNTypeItemArtifact}
+	types := []sdk.CDNItemType{sdk.CDNTypeItemStepLog, sdk.CDNTypeItemServiceLog, sdk.CDNTypeItemRunResult}
 	var storageStats []storage.Stat
 	for _, typ := range types {
 		suStats, err := storage.CountItemsForUnitByType(s.mustDBWithCtx(ctx), storageUnit.ID(), string(typ))
diff --git a/engine/cdn/item/gorp_model.go b/engine/cdn/item/gorp_model.go
index 9646e97b6..59be8d5f1 100644
--- a/engine/cdn/item/gorp_model.go
+++ b/engine/cdn/item/gorp_model.go
@@ -26,8 +26,8 @@ func (c cdnItemDB) ToCDSItem() (sdk.CDNItem, error) {
 			return item, sdk.WithStack(err)
 		}
 		item.APIRef = &apiRef
-	case sdk.CDNTypeItemArtifact:
-		var apiRef sdk.CDNArtifactAPIRef
+	case sdk.CDNTypeItemRunResult:
+		var apiRef sdk.CDNRunResultAPIRef
 		if err := json.Unmarshal(c.APIRefDB, &apiRef); err != nil {
 			return item, sdk.WithStack(err)
 		}
diff --git a/engine/cdn/item_handler.go b/engine/cdn/item_handler.go
index b1e0a7c21..d8fc102e8 100644
--- a/engine/cdn/item_handler.go
+++ b/engine/cdn/item_handler.go
@@ -234,7 +234,7 @@ func (s *Service) getItemsHandler() service.Handler {
 		itemType := sdk.CDNItemType(vars["type"])
 
 		switch itemType {
-		case sdk.CDNTypeItemArtifact:
+		case sdk.CDNTypeItemRunResult:
 			return s.getArtifacts(ctx, r, w)
 		case sdk.CDNTypeItemWorkerCache:
 			return s.getWorkerCache(ctx, r, w)
@@ -249,7 +249,7 @@ func (s *Service) getArtifacts(ctx context.Context, r *http.Request, w http.Resp
 	if runID == "" {
 		return sdk.WrapError(sdk.ErrWrongRequest, "invalid workflow run")
 	}
-	items, err := item.LoadByRunID(ctx, s.Mapper, s.mustDBWithCtx(ctx), sdk.CDNTypeItemArtifact, runID)
+	items, err := item.LoadByRunID(ctx, s.Mapper, s.mustDBWithCtx(ctx), sdk.CDNTypeItemRunResult, runID)
 	if err != nil {
 		return err
 	}
diff --git a/engine/cdn/storage/dao.go b/engine/cdn/storage/dao.go
index 6cee124a4..18421283c 100644
--- a/engine/cdn/storage/dao.go
+++ b/engine/cdn/storage/dao.go
@@ -212,8 +212,8 @@ func CountItemUnitsToDeleteByItemID(db gorp.SqlExecutor, itemID string) (int64,
 	return nb, sdk.WithStack(err)
 }
 
-func LoadAllItemUnitsToDeleteByUnit(ctx context.Context, m *gorpmapper.Mapper, db gorp.SqlExecutor, unitID string, opts ...gorpmapper.GetOptionFunc) ([]sdk.CDNItemUnit, error) {
-	query := gorpmapper.NewQuery("SELECT * FROM storage_unit_item WHERE unit_id = $1 AND to_delete = true ORDER BY last_modified ASC").Args(unitID)
+func LoadAllItemUnitsToDeleteByUnit(ctx context.Context, m *gorpmapper.Mapper, db gorp.SqlExecutor, unitID string, limit int, opts ...gorpmapper.GetOptionFunc) ([]sdk.CDNItemUnit, error) {
+	query := gorpmapper.NewQuery("SELECT * FROM storage_unit_item WHERE unit_id = $1 AND to_delete = true ORDER BY last_modified ASC LIMIT $2").Args(unitID, limit)
 	return getAllItemUnits(ctx, m, db, query, opts...)
 }
 
diff --git a/engine/cdn/storage/storageunit.go b/engine/cdn/storage/storageunit.go
index 44c9089ef..ebd99d82b 100644
--- a/engine/cdn/storage/storageunit.go
+++ b/engine/cdn/storage/storageunit.go
@@ -65,7 +65,13 @@ func Init(ctx context.Context, m *gorpmapper.Mapper, store cache.Store, db *gorp
 		return nil, sdk.WithStack(fmt.Errorf("too much CDN Buffer for file items"))
 	}
 
-	if len(config.Storages) == 0 {
+	activeStorage := 0
+	for _, s := range config.Storages {
+		if !s.DisableSync {
+			activeStorage++
+		}
+	}
+	if activeStorage == 0 {
 		return nil, sdk.WithStack(fmt.Errorf("invalid CDN configuration. Missing storage unit"))
 	}
 
@@ -77,6 +83,14 @@ func Init(ctx context.Context, m *gorpmapper.Mapper, store cache.Store, db *gorp
 		config.SyncSeconds = 30
 	}
 
+	if config.PurgeSeconds <= 0 {
+		config.PurgeSeconds = 30
+	}
+
+	if config.PurgeNbElements <= 0 {
+		config.PurgeNbElements = 1000
+	}
+
 	for name, bu := range config.Buffers {
 		var bufferUnit BufferUnit
 		switch {
@@ -378,7 +392,7 @@ func (r *RunningStorageUnits) Start(ctx context.Context, gorts *sdk.GoRoutines)
 	// 	Feed the sync processes with a ticker
 	gorts.Run(ctx, "RunningStorageUnits.Start", func(ctx context.Context) {
 		tickr := time.NewTicker(time.Duration(r.config.SyncSeconds) * time.Second)
-		tickrPurge := time.NewTicker(30 * time.Second)
+		tickrPurge := time.NewTicker(time.Duration(r.config.PurgeSeconds) * time.Second)
 
 		defer tickr.Stop()
 		defer tickrPurge.Stop()
diff --git a/engine/cdn/storage/storageunit_purge.go b/engine/cdn/storage/storageunit_purge.go
index b27cab40a..c546a54f6 100644
--- a/engine/cdn/storage/storageunit_purge.go
+++ b/engine/cdn/storage/storageunit_purge.go
@@ -20,7 +20,7 @@ func init() {
 }
 
 func (x *RunningStorageUnits) Purge(ctx context.Context, s Interface) error {
-	unitItems, err := LoadAllItemUnitsToDeleteByUnit(ctx, x.m, x.db, s.ID(), gorpmapper.GetOptions.WithDecryption)
+	unitItems, err := LoadAllItemUnitsToDeleteByUnit(ctx, x.m, x.db, s.ID(), x.config.PurgeNbElements, gorpmapper.GetOptions.WithDecryption)
 	if err != nil {
 		return err
 	}
diff --git a/engine/cdn/storage/types.go b/engine/cdn/storage/types.go
index c4d8cc00b..2b5eaedda 100644
--- a/engine/cdn/storage/types.go
+++ b/engine/cdn/storage/types.go
@@ -149,6 +149,8 @@ type Configuration struct {
 	Storages        map[string]StorageConfiguration `toml:"storages" json:"storages" mapstructure:"storages"`
 	SyncSeconds     int                             `toml:"syncSeconds" default:"30" json:"syncSeconds" comment:"each n seconds, all storage backends will have to start a synchronization with the buffer"`
 	SyncNbElements  int64                           `toml:"syncNbElements" default:"100" json:"syncNbElements" comment:"nb items to synchronize from the buffer"`
+	PurgeSeconds    int                             `toml:"purgeSeconds" default:"5" json:"purgeSeconds" comment:"each n seconds, all storage backends will have to start to delete storage unit item with deleted flag"`
+	PurgeNbElements int                             `toml:"purgeNbElements" default:"1000" json:"purgeNbElements" comment:"nb items to delete in each purge loop"`
 }
 
 type BufferConfiguration struct {
diff --git a/engine/hooks/tasks.go b/engine/hooks/tasks.go
index c7811b900..95ff8534c 100644
--- a/engine/hooks/tasks.go
+++ b/engine/hooks/tasks.go
@@ -440,6 +440,7 @@ func (s *Service) doTask(ctx context.Context, t *sdk.Task, e *sdk.TaskExecution)
 		run, err := s.Client.WorkflowRunFromHook(confProj.Value, confWorkflow.Value, hEvent)
 		if err != nil {
 			globalErr = err
+			ctx := sdk.ContextWithStacktrace(ctx, err)
 			log.Warn(ctx, "Hooks> %s > unable to run workflow %s/%s : %v", t.UUID, confProj.Value, confWorkflow.Value, err)
 		} else {
 			//Save the run number
diff --git a/engine/worker/internal/action/builtin_artifact_download.go b/engine/worker/internal/action/builtin_artifact_download.go
index 036e21118..443b69750 100644
--- a/engine/worker/internal/action/builtin_artifact_download.go
+++ b/engine/worker/internal/action/builtin_artifact_download.go
@@ -89,7 +89,7 @@ func RunArtifactDownload(ctx context.Context, wk workerruntime.Runtime, a sdk.Ac
 	wg.Add(len(cdnItems.Items))
 	for i := range cdnItems.Items {
 		item := cdnItems.Items[i]
-		apiRef, is := item.GetCDNArtifactApiRef()
+		apiRef, is := item.GetCDNRunResultApiRef()
 		if !is {
 			res.Status = sdk.StatusFail
 			res.Reason = fmt.Sprintf("item %s is not an artifact", item.ID)
@@ -114,7 +114,7 @@ func RunArtifactDownload(ctx context.Context, wk workerruntime.Runtime, a sdk.Ac
 			}
 			wk.SendLog(ctx, workerruntime.LevelInfo, fmt.Sprintf("Downloading artifact %s from workflow %s/%s on run %d...", destFile, project, workflow, n))
 
-			r, err := wk.Client().CDNItemDownload(ctx, wk.CDNHttpURL(), item.APIRefHash, sdk.CDNTypeItemArtifact)
+			r, err := wk.Client().CDNItemDownload(ctx, wk.CDNHttpURL(), item.APIRefHash, sdk.CDNTypeItemRunResult)
 			if err != nil {
 				res.Status = sdk.StatusFail
 				res.Reason = err.Error()
diff --git a/engine/worker/internal/action/builtin_artifact_upload.go b/engine/worker/internal/action/builtin_artifact_upload.go
index d70eaa7af..2e293c497 100644
--- a/engine/worker/internal/action/builtin_artifact_upload.go
+++ b/engine/worker/internal/action/builtin_artifact_upload.go
@@ -109,7 +109,7 @@ func RunArtifactUpload(ctx context.Context, wk workerruntime.Runtime, a sdk.Acti
 				wgErrors.Add(1)
 				return
 			}
-			signature, err := wk.ArtifactSignature(name, uint32(fileMode.Mode().Perm()))
+			signature, err := wk.RunResultSignature(name, uint32(fileMode.Mode().Perm()), sdk.WorkflowRunResultTypeArtifact)
 			if err != nil {
 				log.Error(ctx, "unable to sign artifact: %v", err)
 				return
diff --git a/engine/worker/internal/handler_download.go b/engine/worker/internal/handler_download.go
index b857c4f73..a28c0fb39 100644
--- a/engine/worker/internal/handler_download.go
+++ b/engine/worker/internal/handler_download.go
@@ -131,7 +131,7 @@ func downloadHandler(ctx context.Context, wk *CurrentWorker) http.HandlerFunc {
 		wg.Add(len(cdnItems.Items))
 		for i := range cdnItems.Items {
 			item := cdnItems.Items[i]
-			apiRef, is := item.GetCDNArtifactApiRef()
+			apiRef, is := item.GetCDNRunResultApiRef()
 			if !is {
 				newError := sdk.NewError(sdk.ErrInvalidData, fmt.Errorf("item is not an artifact: %v", err))
 				writeError(w, r, newError)
@@ -154,7 +154,7 @@ func downloadHandler(ctx context.Context, wk *CurrentWorker) http.HandlerFunc {
 				}
 				wk.SendLog(ctx, workerruntime.LevelInfo, fmt.Sprintf("Downloading artifact %s from workflow %s/%s on run %d...", destFile, projectKey, reqArgs.Workflow, reqArgs.Number))
 
-				reader, err := wk.Client().CDNItemDownload(ctx, wk.CDNHttpURL(), item.APIRefHash, sdk.CDNTypeItemArtifact)
+				reader, err := wk.Client().CDNItemDownload(ctx, wk.CDNHttpURL(), item.APIRefHash, sdk.CDNTypeItemRunResult)
 				if err != nil {
 					newError := sdk.NewError(sdk.ErrUnknownError, fmt.Errorf("cannot download artifact %s: %s", destFile, err))
 					writeError(w, r, newError)
diff --git a/engine/worker/internal/types.go b/engine/worker/internal/types.go
index 72d47f301..99074d5ba 100644
--- a/engine/worker/internal/types.go
+++ b/engine/worker/internal/types.go
@@ -139,7 +139,7 @@ func (wk *CurrentWorker) WorkerCacheSignature(tag string) (string, error) {
 	return signature, sdk.WrapError(err, "cannot sign log message")
 }
 
-func (wk *CurrentWorker) ArtifactSignature(artifactName string, perm uint32) (string, error) {
+func (wk *CurrentWorker) RunResultSignature(artifactName string, perm uint32, t sdk.WorkflowRunResultType) (string, error) {
 	sig := cdn.Signature{
 		ProjectKey:   wk.currentJob.projectKey,
 		JobID:        wk.currentJob.wJob.ID,
@@ -152,10 +152,11 @@ func (wk *CurrentWorker) ArtifactSignature(artifactName string, perm uint32) (st
 		RunNumber:    wk.currentJob.runNumber,
 		JobName:      wk.currentJob.wJob.Job.Action.Name,
 		Worker: &cdn.SignatureWorker{
-			WorkerID:     wk.id,
-			WorkerName:   wk.Name(),
-			ArtifactName: artifactName,
-			FilePerm:     perm,
+			WorkerID:      wk.id,
+			WorkerName:    wk.Name(),
+			FileName:      artifactName,
+			FilePerm:      perm,
+			RunResultType: string(t),
 		},
 	}
 	signature, err := jws.Sign(wk.currentJob.signer, sig)
diff --git a/engine/worker/pkg/workerruntime/types.go b/engine/worker/pkg/workerruntime/types.go
index 3be6d7429..c8a077664 100644
--- a/engine/worker/pkg/workerruntime/types.go
+++ b/engine/worker/pkg/workerruntime/types.go
@@ -73,7 +73,7 @@ type Runtime interface {
 	Take(ctx context.Context, job sdk.WorkflowNodeJobRun) error
 	ProcessJob(job sdk.WorkflowNodeJobRunData) sdk.Result
 	SendLog(ctx context.Context, level Level, format string)
-	ArtifactSignature(artifactName string, perm uint32) (string, error)
+	RunResultSignature(fileName string, perm uint32, t sdk.WorkflowRunResultType) (string, error)
 	WorkerCacheSignature(tag string) (string, error)
 	FeatureEnabled(featureName sdk.FeatureName) bool
 	CDNHttpURL() string
diff --git a/sdk/cdn.go b/sdk/cdn.go
index 00902d054..024cc46b8 100644
--- a/sdk/cdn.go
+++ b/sdk/cdn.go
@@ -64,8 +64,8 @@ func (c *CDNItem) UnmarshalJSON(data []byte) error {
 			return WithStack(err)
 		}
 		itemAlias.APIRef = &apiRef
-	case CDNTypeItemArtifact:
-		var apiRef CDNArtifactAPIRef
+	case CDNTypeItemRunResult:
+		var apiRef CDNRunResultAPIRef
 		if err := json.Unmarshal(itemAlias.APIRefRaw, &apiRef); err != nil {
 			return WithStack(err)
 		}
@@ -150,16 +150,17 @@ type CDNLogAPIRef struct {
 	RequirementServiceName string `json:"service_name,omitempty"`
 }
 
-type CDNArtifactAPIRef struct {
-	ProjectKey   string `json:"project_key"`
-	WorkflowName string `json:"workflow_name"`
-	WorkflowID   int64  `json:"workflow_id"`
-	RunID        int64  `json:"run_id"`
-	RunJobID     int64  `json:"run_job_id"`
-	RunJobName   string `json:"run_job_name"`
-	RunNodeID    int64  `json:"run_node_id"`
-	ArtifactName string `json:"artifact_name"`
-	Perm         uint32 `json:"perm"`
+type CDNRunResultAPIRef struct {
+	ProjectKey    string                `json:"project_key"`
+	WorkflowName  string                `json:"workflow_name"`
+	WorkflowID    int64                 `json:"workflow_id"`
+	RunID         int64                 `json:"run_id"`
+	RunJobID      int64                 `json:"run_job_id"`
+	RunJobName    string                `json:"run_job_name"`
+	RunNodeID     int64                 `json:"run_node_id"`
+	ArtifactName  string                `json:"artifact_name"`
+	Perm          uint32                `json:"perm"`
+	RunResultType WorkflowRunResultType `json:"type"`
 }
 
 type CDNWorkerCacheAPIRef struct {
@@ -172,8 +173,8 @@ func NewCDNApiRef(t CDNItemType, signature cdn.Signature) (CDNApiRef, error) {
 	switch t {
 	case CDNTypeItemStepLog, CDNTypeItemServiceLog:
 		return NewCDNLogApiRef(signature), nil
-	case CDNTypeItemArtifact:
-		return NewCDNArtifactApiRef(signature), nil
+	case CDNTypeItemRunResult:
+		return NewCDNRunResultApiRef(signature), nil
 	case CDNTypeItemWorkerCache:
 		return NewCDNWorkerCacheApiRef(signature), nil
 	}
@@ -189,18 +190,19 @@ func NewCDNWorkerCacheApiRef(signature cdn.Signature) CDNApiRef {
 	return &apiRef
 }
 
-func NewCDNArtifactApiRef(signature cdn.Signature) CDNApiRef {
+func NewCDNRunResultApiRef(signature cdn.Signature) CDNApiRef {
 	// Build cds api ref
-	apiRef := CDNArtifactAPIRef{
-		ProjectKey:   signature.ProjectKey,
-		WorkflowName: signature.WorkflowName,
-		WorkflowID:   signature.WorkflowID,
-		RunID:        signature.RunID,
-		RunNodeID:    signature.NodeRunID,
-		RunJobName:   signature.JobName,
-		RunJobID:     signature.JobID,
-		ArtifactName: signature.Worker.ArtifactName,
-		Perm:         signature.Worker.FilePerm,
+	apiRef := CDNRunResultAPIRef{
+		ProjectKey:    signature.ProjectKey,
+		WorkflowName:  signature.WorkflowName,
+		WorkflowID:    signature.WorkflowID,
+		RunID:         signature.RunID,
+		RunNodeID:     signature.NodeRunID,
+		RunJobName:    signature.JobName,
+		RunJobID:      signature.JobID,
+		ArtifactName:  signature.Worker.FileName,
+		Perm:          signature.Worker.FilePerm,
+		RunResultType: WorkflowRunResultType(signature.Worker.RunResultType),
 	}
 	return &apiRef
 }
@@ -258,8 +260,8 @@ func (c CDNItem) GetCDNLogApiRef() (*CDNLogAPIRef, bool) {
 	return apiRef, has
 }
 
-func (c CDNItem) GetCDNArtifactApiRef() (*CDNArtifactAPIRef, bool) {
-	apiRef, has := c.APIRef.(*CDNArtifactAPIRef)
+func (c CDNItem) GetCDNRunResultApiRef() (*CDNRunResultAPIRef, bool) {
+	apiRef, has := c.APIRef.(*CDNRunResultAPIRef)
 	return apiRef, has
 }
 
@@ -293,11 +295,11 @@ func (a *CDNLogAPIRef) ToHash() (string, error) {
 	return strconv.FormatUint(hashRefU, 10), nil
 }
 
-func (a *CDNArtifactAPIRef) ToFilename() string {
+func (a *CDNRunResultAPIRef) ToFilename() string {
 	return a.ArtifactName
 }
 
-func (a *CDNArtifactAPIRef) ToHash() (string, error) {
+func (a *CDNRunResultAPIRef) ToHash() (string, error) {
 	m := make(map[string]string, 7)
 	m["project_key"] = a.ProjectKey
 	m["workflow_name"] = a.WorkflowName
@@ -336,7 +338,7 @@ type CDNItemType string
 
 func (t CDNItemType) Validate() error {
 	switch t {
-	case CDNTypeItemStepLog, CDNTypeItemServiceLog, CDNTypeItemArtifact, CDNTypeItemWorkerCache:
+	case CDNTypeItemStepLog, CDNTypeItemServiceLog, CDNTypeItemRunResult, CDNTypeItemWorkerCache:
 		return nil
 	}
 	return NewErrorFrom(ErrWrongRequest, "invalid item type")
@@ -353,7 +355,7 @@ func (t CDNItemType) IsLog() bool {
 const (
 	CDNTypeItemStepLog     CDNItemType = "step-log"
 	CDNTypeItemServiceLog  CDNItemType = "service-log"
-	CDNTypeItemArtifact    CDNItemType = "artifact"
+	CDNTypeItemRunResult   CDNItemType = "run-result"
 	CDNTypeItemWorkerCache CDNItemType = "worker-cache"
 	CDNStatusItemIncoming              = "Incoming"
 	CDNStatusItemCompleted             = "Completed"
diff --git a/sdk/cdn/signature.go b/sdk/cdn/signature.go
index 0b21815a2..11fae2abd 100644
--- a/sdk/cdn/signature.go
+++ b/sdk/cdn/signature.go
@@ -16,13 +16,14 @@ type Signature struct {
 }
 
 type SignatureWorker struct {
-	WorkerID     string
-	WorkerName   string
-	StepOrder    int64
-	StepName     string
-	ArtifactName string
-	FilePerm     uint32
-	CacheTag     string
+	WorkerID      string
+	WorkerName    string
+	StepOrder     int64
+	StepName      string
+	FileName      string
+	FilePerm      uint32
+	CacheTag      string
+	RunResultType string
 }
 
 type SignatureService struct {
diff --git a/sdk/cdsclient/client_workflow.go b/sdk/cdsclient/client_workflow.go
index 8cfed6565..02889f6f5 100644
--- a/sdk/cdsclient/client_workflow.go
+++ b/sdk/cdsclient/client_workflow.go
@@ -478,9 +478,9 @@ func (c *client) WorkflowRunResultsAdd(ctx context.Context, projectKey string, n
 	return nil
 }
 
-func (c *client) WorkflowRunArtifactCheck(ctx context.Context, projectKey string, name string, number int64, artifactRef sdk.CDNArtifactAPIRef) error {
-	uri := fmt.Sprintf("/project/%s/workflows/%s/runs/%d/artifacts/check", projectKey, name, number)
-	if _, err := c.PostJSON(ctx, uri, artifactRef, nil); err != nil {
+func (c *client) WorkflowRunResultCheck(ctx context.Context, projectKey string, name string, number int64, apiRef sdk.CDNRunResultAPIRef) error {
+	uri := fmt.Sprintf("/project/%s/workflows/%s/runs/%d/results/check", projectKey, name, number)
+	if _, err := c.PostJSON(ctx, uri, apiRef, nil); err != nil {
 		return err
 	}
 	return nil
diff --git a/sdk/cdsclient/interface.go b/sdk/cdsclient/interface.go
index 56e4e5457..5b5f3a625 100644
--- a/sdk/cdsclient/interface.go
+++ b/sdk/cdsclient/interface.go
@@ -336,7 +336,7 @@ type WorkflowClient interface {
 	WorkflowRunList(projectKey string, workflowName string, offset, limit int64) ([]sdk.WorkflowRun, error)
 	WorkflowRunArtifacts(projectKey string, name string, number int64) ([]sdk.WorkflowNodeRunArtifact, error)
 	WorkflowRunArtifactsLinks(projectKey string, name string, number int64) (sdk.CDNItemLinks, error)
-	WorkflowRunArtifactCheck(ctx context.Context, projectKey string, name string, number int64, artifactRef sdk.CDNArtifactAPIRef) error
+	WorkflowRunResultCheck(ctx context.Context, projectKey string, name string, number int64, apiRef sdk.CDNRunResultAPIRef) error
 	WorkflowRunResultsAdd(ctx context.Context, projectKey string, name string, number int64, addRequest sdk.WorkflowRunResult) error
 	WorkflowRunResultsList(ctx context.Context, projectKey string, name string, number int64) ([]sdk.WorkflowRunResult, error)
 	WorkflowRunFromHook(projectKey string, workflowName string, hook sdk.WorkflowNodeRunHookEvent) (*sdk.WorkflowRun, error)
diff --git a/sdk/cdsclient/mock_cdsclient/interface_mock.go b/sdk/cdsclient/mock_cdsclient/interface_mock.go
index 0b57bb296..78503cd8d 100644
--- a/sdk/cdsclient/mock_cdsclient/interface_mock.go
+++ b/sdk/cdsclient/mock_cdsclient/interface_mock.go
@@ -4129,18 +4129,18 @@ func (mr *MockWorkflowClientMockRecorder) WorkflowRunArtifactsLinks(projectKey,
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WorkflowRunArtifactsLinks", reflect.TypeOf((*MockWorkflowClient)(nil).WorkflowRunArtifactsLinks), projectKey, name, number)
 }
 
-// WorkflowRunArtifactCheck mocks base method
-func (m *MockWorkflowClient) WorkflowRunArtifactCheck(ctx context.Context, projectKey, name string, number int64, artifactRef sdk.CDNArtifactAPIRef) error {
+// WorkflowRunResultCheck mocks base method
+func (m *MockWorkflowClient) WorkflowRunResultCheck(ctx context.Context, projectKey, name string, number int64, apiRef sdk.CDNRunResultAPIRef) error {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "WorkflowRunArtifactCheck", ctx, projectKey, name, number, artifactRef)
+	ret := m.ctrl.Call(m, "WorkflowRunResultCheck", ctx, projectKey, name, number, apiRef)
 	ret0, _ := ret[0].(error)
 	return ret0
 }
 
-// WorkflowRunArtifactCheck indicates an expected call of WorkflowRunArtifactCheck
-func (mr *MockWorkflowClientMockRecorder) WorkflowRunArtifactCheck(ctx, projectKey, name, number, artifactRef interface{}) *gomock.Call {
+// WorkflowRunResultCheck indicates an expected call of WorkflowRunResultCheck
+func (mr *MockWorkflowClientMockRecorder) WorkflowRunResultCheck(ctx, projectKey, name, number, apiRef interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WorkflowRunArtifactCheck", reflect.TypeOf((*MockWorkflowClient)(nil).WorkflowRunArtifactCheck), ctx, projectKey, name, number, artifactRef)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WorkflowRunResultCheck", reflect.TypeOf((*MockWorkflowClient)(nil).WorkflowRunResultCheck), ctx, projectKey, name, number, apiRef)
 }
 
 // WorkflowRunResultsAdd mocks base method
@@ -8192,18 +8192,18 @@ func (mr *MockInterfaceMockRecorder) WorkflowRunArtifactsLinks(projectKey, name,
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WorkflowRunArtifactsLinks", reflect.TypeOf((*MockInterface)(nil).WorkflowRunArtifactsLinks), projectKey, name, number)
 }
 
-// WorkflowRunArtifactCheck mocks base method
-func (m *MockInterface) WorkflowRunArtifactCheck(ctx context.Context, projectKey, name string, number int64, artifactRef sdk.CDNArtifactAPIRef) error {
+// WorkflowRunResultCheck mocks base method
+func (m *MockInterface) WorkflowRunResultCheck(ctx context.Context, projectKey, name string, number int64, apiRef sdk.CDNRunResultAPIRef) error {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "WorkflowRunArtifactCheck", ctx, projectKey, name, number, artifactRef)
+	ret := m.ctrl.Call(m, "WorkflowRunResultCheck", ctx, projectKey, name, number, apiRef)
 	ret0, _ := ret[0].(error)
 	return ret0
 }
 
-// WorkflowRunArtifactCheck indicates an expected call of WorkflowRunArtifactCheck
-func (mr *MockInterfaceMockRecorder) WorkflowRunArtifactCheck(ctx, projectKey, name, number, artifactRef interface{}) *gomock.Call {
+// WorkflowRunResultCheck indicates an expected call of WorkflowRunResultCheck
+func (mr *MockInterfaceMockRecorder) WorkflowRunResultCheck(ctx, projectKey, name, number, apiRef interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WorkflowRunArtifactCheck", reflect.TypeOf((*MockInterface)(nil).WorkflowRunArtifactCheck), ctx, projectKey, name, number, artifactRef)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WorkflowRunResultCheck", reflect.TypeOf((*MockInterface)(nil).WorkflowRunResultCheck), ctx, projectKey, name, number, apiRef)
 }
 
 // WorkflowRunResultsAdd mocks base method
diff --git a/sdk/workflow_run_result.go b/sdk/workflow_run_result.go
index ca2c516d8..59997861d 100644
--- a/sdk/workflow_run_result.go
+++ b/sdk/workflow_run_result.go
@@ -7,6 +7,7 @@ import (
 
 const (
 	WorkflowRunResultTypeArtifact WorkflowRunResultType = "artifact"
+	WorkflowRunResultTypeCoverage WorkflowRunResultType = "coverage"
 )
 
 type WorkflowRunResultType string
@@ -31,6 +32,14 @@ func (r *WorkflowRunResult) GetArtifact() (WorkflowRunResultArtifact, error) {
 	return data, nil
 }
 
+func (r *WorkflowRunResult) GetCoverage() (WorkflowRunResultCoverage, error) {
+	var data WorkflowRunResultCoverage
+	if err := json.Unmarshal(r.DataRaw, &data); err != nil {
+		return data, WithStack(err)
+	}
+	return data, nil
+}
+
 type WorkflowRunResultArtifact struct {
 	Name       string `json:"name"`
 	Size       int64  `json:"size"`
@@ -57,3 +66,26 @@ func (a *WorkflowRunResultArtifact) IsValid() error {
 	}
 	return nil
 }
+
+type WorkflowRunResultCoverage struct {
+	Name       string `json:"name"`
+	Size       int64  `json:"size"`
+	MD5        string `json:"md5"`
+	CDNRefHash string `json:"cdn_hash"`
+}
+
+func (a *WorkflowRunResultCoverage) IsValid() error {
+	if a.Name == "" {
+		return WrapError(ErrInvalidData, "missing file name")
+	}
+	if a.Size == 0 {
+		return WrapError(ErrInvalidData, "missing file size")
+	}
+	if a.MD5 == "" {
+		return WrapError(ErrInvalidData, "missing md5Sum")
+	}
+	if a.CDNRefHash == "" {
+		return WrapError(ErrInvalidData, "missing cdn item hash")
+	}
+	return nil
+}


Test patch:
diff --git a/engine/api/workflow/workflow_run_results_test.go b/engine/api/workflow/workflow_run_results_test.go
index fd0be3747..f2b2acf93 100644
--- a/engine/api/workflow/workflow_run_results_test.go
+++ b/engine/api/workflow/workflow_run_results_test.go
@@ -67,7 +67,7 @@ func TestCanUploadArtifactTerminatedJob(t *testing.T) {
 
 	proj, wk, workflowRun, nodeRun, jobRun := createRunNodeRunAndJob(t, db, store)
 
-	artifactRef := sdk.CDNArtifactAPIRef{
+	artifactRef := sdk.CDNRunResultAPIRef{
 		ProjectKey:   proj.Name,
 		WorkflowName: wk.Name,
 		WorkflowID:   wk.ID,
@@ -80,7 +80,7 @@ func TestCanUploadArtifactTerminatedJob(t *testing.T) {
 	jobRun.Status = sdk.StatusSuccess
 	require.NoError(t, workflow.UpdateNodeJobRun(ctx, db, &jobRun))
 
-	_, err := workflow.CanUploadArtifact(ctx, db.DbMap, store, workflowRun, artifactRef)
+	_, err := workflow.CanUploadRunResult(ctx, db.DbMap, store, workflowRun, artifactRef)
 	require.True(t, sdk.ErrorIs(err, sdk.ErrInvalidData))
 	require.Contains(t, err.Error(), "unable to upload artifact on a terminated job")
 }
@@ -91,7 +91,7 @@ func TestCanUploadArtifactWrongNodeRun(t *testing.T) {
 
 	proj, wk, workflowRun, nodeRun, jobRun := createRunNodeRunAndJob(t, db, store)
 
-	artifactRef := sdk.CDNArtifactAPIRef{
+	artifactRef := sdk.CDNRunResultAPIRef{
 		ProjectKey:   proj.Key,
 		WorkflowName: wk.Name,
 		WorkflowID:   wk.ID,
@@ -101,7 +101,7 @@ func TestCanUploadArtifactWrongNodeRun(t *testing.T) {
 		ArtifactName: "myartifact",
 	}
 
-	_, err := workflow.CanUploadArtifact(ctx, db.DbMap, store, workflowRun, artifactRef)
+	_, err := workflow.CanUploadRunResult(ctx, db.DbMap, store, workflowRun, artifactRef)
 	require.True(t, sdk.ErrorIs(err, sdk.ErrNotFound))
 	require.Contains(t, err.Error(), "unable to find node run")
 }
@@ -112,14 +112,15 @@ func TestCanUploadArtifactAlreadyExist(t *testing.T) {
 
 	proj, wk, workflowRun, nodeRun, jobRun := createRunNodeRunAndJob(t, db, store)
 
-	artifactRef := sdk.CDNArtifactAPIRef{
-		ProjectKey:   proj.Key,
-		WorkflowName: wk.Name,
-		WorkflowID:   wk.ID,
-		RunJobID:     jobRun.ID,
-		RunNodeID:    nodeRun.ID,
-		RunID:        workflowRun.ID,
-		ArtifactName: "myartifact",
+	artifactRef := sdk.CDNRunResultAPIRef{
+		ProjectKey:    proj.Key,
+		WorkflowName:  wk.Name,
+		WorkflowID:    wk.ID,
+		RunJobID:      jobRun.ID,
+		RunNodeID:     nodeRun.ID,
+		RunID:         workflowRun.ID,
+		ArtifactName:  "myartifact",
+		RunResultType: sdk.WorkflowRunResultTypeArtifact,
 	}
 
 	result := sdk.WorkflowRunResult{
@@ -141,14 +142,14 @@ func TestCanUploadArtifactAlreadyExist(t *testing.T) {
 	bts, err := json.Marshal(artiData)
 	result.DataRaw = bts
 
-	cacheKey := workflow.GetArtifactResultKey(result.WorkflowRunID, artiData.Name)
+	cacheKey := workflow.GetRunResultKey(result.WorkflowRunID, sdk.WorkflowRunResultTypeArtifact, artiData.Name)
 	require.NoError(t, store.SetWithTTL(cacheKey, true, 60))
 	require.NoError(t, workflow.AddResult(db.DbMap, store, &result))
 	b, err := store.Exist(cacheKey)
 	require.NoError(t, err)
 	require.False(t, b)
 
-	_, err = workflow.CanUploadArtifact(ctx, db.DbMap, store, workflowRun, artifactRef)
+	_, err = workflow.CanUploadRunResult(ctx, db.DbMap, store, workflowRun, artifactRef)
 	require.True(t, sdk.ErrorIs(err, sdk.ErrConflictData))
 	require.Contains(t, err.Error(), "artifact myartifact has already been uploaded")
 }
@@ -159,14 +160,15 @@ func TestCanUploadArtifactAlreadyExistInMoreRecentSubNum(t *testing.T) {
 
 	proj, wk, workflowRun, nodeRun, jobRun := createRunNodeRunAndJob(t, db, store)
 
-	artifactRef := sdk.CDNArtifactAPIRef{
-		ProjectKey:   proj.Key,
-		WorkflowName: wk.Name,
-		WorkflowID:   wk.ID,
-		RunJobID:     jobRun.ID,
-		RunNodeID:    nodeRun.ID,
-		RunID:        workflowRun.ID,
-		ArtifactName: "myartifact",
+	artifactRef := sdk.CDNRunResultAPIRef{
+		ProjectKey:    proj.Key,
+		WorkflowName:  wk.Name,
+		WorkflowID:    wk.ID,
+		RunJobID:      jobRun.ID,
+		RunNodeID:     nodeRun.ID,
+		RunID:         workflowRun.ID,
+		ArtifactName:  "myartifact",
+		RunResultType: sdk.WorkflowRunResultTypeArtifact,
 	}
 
 	result := sdk.WorkflowRunResult{
@@ -188,14 +190,14 @@ func TestCanUploadArtifactAlreadyExistInMoreRecentSubNum(t *testing.T) {
 	bts, err := json.Marshal(artiData)
 	result.DataRaw = bts
 
-	cacheKey := workflow.GetArtifactResultKey(result.WorkflowRunID, artiData.Name)
+	cacheKey := workflow.GetRunResultKey(result.WorkflowRunID, sdk.WorkflowRunResultTypeArtifact, artiData.Name)
 	require.NoError(t, store.SetWithTTL(cacheKey, true, 60))
 	require.NoError(t, workflow.AddResult(db.DbMap, store, &result))
 	b, err := store.Exist(cacheKey)
 	require.NoError(t, err)
 	require.False(t, b)
 
-	_, err = workflow.CanUploadArtifact(ctx, db.DbMap, store, workflowRun, artifactRef)
+	_, err = workflow.CanUploadRunResult(ctx, db.DbMap, store, workflowRun, artifactRef)
 	require.True(t, sdk.ErrorIs(err, sdk.ErrConflictData))
 	require.Contains(t, err.Error(), "artifact myartifact cannot be uploaded into a previous run")
 }
@@ -220,7 +222,7 @@ func TestCanUploadArtifactAlreadyExistInAPreviousSubNum(t *testing.T) {
 	require.NoError(t, err)
 	workflowRun = *run2
 
-	artifactRef := sdk.CDNArtifactAPIRef{
+	artifactRef := sdk.CDNRunResultAPIRef{
 		ProjectKey:   proj.Key,
 		WorkflowName: wk.Name,
 		WorkflowID:   wk.ID,
@@ -249,13 +251,13 @@ func TestCanUploadArtifactAlreadyExistInAPreviousSubNum(t *testing.T) {
 	bts, err := json.Marshal(artiData)
 	result.DataRaw = bts
 
-	cacheKey := workflow.GetArtifactResultKey(result.WorkflowRunID, artiData.Name)
+	cacheKey := workflow.GetRunResultKey(result.WorkflowRunID, sdk.WorkflowRunResultTypeArtifact, artiData.Name)
 	require.NoError(t, store.SetWithTTL(cacheKey, true, 60))
 	require.NoError(t, workflow.AddResult(db.DbMap, store, &result))
 	b, err := store.Exist(cacheKey)
 	require.NoError(t, err)
 	require.False(t, b)
 
-	_, err = workflow.CanUploadArtifact(ctx, db.DbMap, store, workflowRun, artifactRef)
+	_, err = workflow.CanUploadRunResult(ctx, db.DbMap, store, workflowRun, artifactRef)
 	require.NoError(t, err)
 }
diff --git a/engine/api/workflow_run_result_test.go b/engine/api/workflow_run_result_test.go
index fbf8fbb60..454414ece 100644
--- a/engine/api/workflow_run_result_test.go
+++ b/engine/api/workflow_run_result_test.go
@@ -54,7 +54,7 @@ func Test_getWorkflowRunAndNodeRunResults(t *testing.T) {
 		WorkflowRunID:     wrDB.ID,
 		DataRaw:           bts,
 	}
-	api.Cache.SetWithTTL(workflow.GetArtifactResultKey(wrCreate.ID, artiData.Name), true, 60)
+	api.Cache.SetWithTTL(workflow.GetRunResultKey(wrCreate.ID, sdk.WorkflowRunResultTypeArtifact, artiData.Name), true, 60)
 	require.NoError(t, workflow.AddResult(db.DbMap, api.Cache, &result))
 
 	//Prepare request
@@ -188,7 +188,7 @@ func Test_workflowRunResultsAdd(t *testing.T) {
 	assert.Equal(t, 403, rec.Code)
 
 	// add check
-	require.NoError(t, api.Cache.SetWithTTL(workflow.GetArtifactResultKey(wrCreate.ID, artiData.Name), true, 60))
+	require.NoError(t, api.Cache.SetWithTTL(workflow.GetRunResultKey(wrCreate.ID, sdk.WorkflowRunResultTypeArtifact, artiData.Name), true, 60))
 
 	//Do the request
 	reqOK := assets.NewJWTAuthentifiedRequest(t, jwtCDN, "POST", uri, addResultRequest)
@@ -196,13 +196,13 @@ func Test_workflowRunResultsAdd(t *testing.T) {
 	router.Mux.ServeHTTP(recOK, reqOK)
 	assert.Equal(t, 204, recOK.Code)
 
-	b, err := api.Cache.Exist(workflow.GetArtifactResultKey(wrCreate.ID, artiData.Name))
+	b, err := api.Cache.Exist(workflow.GetRunResultKey(wrCreate.ID, sdk.WorkflowRunResultTypeArtifact, artiData.Name))
 	require.NoError(t, err)
 	require.False(t, b)
 
 }
 
-func Test_workflowRunArtifactCheckUpload(t *testing.T) {
+func Test_workflowRunResultCheckUpload(t *testing.T) {
 	featureflipping.Init(gorpmapping.Mapper)
 	api, db, router := newTestAPI(t, bootstrap.InitiliazeDB)
 
@@ -249,7 +249,7 @@ func Test_workflowRunArtifactCheckUpload(t *testing.T) {
 		"number":           fmt.Sprintf("%d", wrCreate.Number),
 		"nodeID":           fmt.Sprintf("%d", wrCreate.Workflow.WorkflowData.Node.ID),
 	}
-	checkRequest := sdk.CDNArtifactAPIRef{
+	checkRequest := sdk.CDNRunResultAPIRef{
 		ArtifactName: "myArtifact",
 		RunID:        wrCreate.ID,
 		RunNodeID:    nr.ID,
@@ -259,7 +259,7 @@ func Test_workflowRunArtifactCheckUpload(t *testing.T) {
 		ProjectKey:   key,
 	}
 
-	uri := router.GetRoute("POST", api.workflowRunArtifactCheckUploadHandler, vars)
+	uri := router.GetRoute("POST", api.workflowRunResultCheckUploadHandler, vars)
 	test.NotEmpty(t, uri)
 	req := assets.NewJWTAuthentifiedRequest(t, jwtCDN, "POST", uri, checkRequest)
 
diff --git a/engine/cdn/cdn_gc_test.go b/engine/cdn/cdn_gc_test.go
index df52cc04e..f6258bc0f 100644
--- a/engine/cdn/cdn_gc_test.go
+++ b/engine/cdn/cdn_gc_test.go
@@ -188,6 +188,152 @@ func TestCleanSynchronizedItem(t *testing.T) {
 	require.Equal(t, 3, len(iusCDSAfter))
 }
 
+func TestCleanSynchronizedItemWithDisabledStorage(t *testing.T) {
+	m := gorpmapper.New()
+	item.InitDBMapping(m)
+	storage.InitDBMapping(m)
+
+	log.Factory = log.NewTestingWrapper(t)
+	db, factory, cache, cancel := test.SetupPGToCancel(t, m, sdk.TypeCDN)
+	t.Cleanup(cancel)
+
+	cfg := test.LoadTestingConf(t, sdk.TypeCDN)
+
+	cdntest.ClearItem(t, context.TODO(), m, db)
+	cdntest.ClearUnits(t, context.TODO(), m, db)
+
+	// Create cdn service
+	s := Service{
+		DBConnectionFactory: factory,
+		Cache:               cache,
+		Mapper:              m,
+	}
+	s.GoRoutines = sdk.NewGoRoutines()
+
+	tmpDir, err := ioutil.TempDir("", t.Name()+"-cdn-1-*")
+	require.NoError(t, err)
+
+	ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second)
+	t.Cleanup(cancel)
+
+	cdnUnits, err := storage.Init(ctx, m, cache, db.DbMap, sdk.NewGoRoutines(), storage.Configuration{
+		HashLocatorSalt: "thisismysalt",
+		Buffers: map[string]storage.BufferConfiguration{
+			"redis_buffer": {
+				Redis: &storage.RedisBufferConfiguration{
+					Host:     cfg["redisHost"],
+					Password: cfg["redisPassword"],
+				},
+				BufferType: storage.CDNBufferTypeLog,
+			},
+		},
+		Storages: map[string]storage.StorageConfiguration{
+			"fs-backend": {
+				Local: &storage.LocalStorageConfiguration{
+					Path: tmpDir,
+					Encryption: []convergent.ConvergentEncryptionConfig{
+						{
+							Cipher:      aesgcm.CipherName,
+							LocatorSalt: "secret_locator_salt",
+							SecretValue: "secret_value",
+						},
+					},
+				},
+			},
+			"cds-backend": {
+				DisableSync: true,
+				CDS: &storage.CDSStorageConfiguration{
+					Host:  "lolcat.host",
+					Token: "mytoken",
+				},
+			},
+		},
+	})
+	require.NoError(t, err)
+	s.Units = cdnUnits
+
+	// Add item in redis and CDS -- Stay in redis
+	item1RedisCDS := sdk.CDNItem{
+		ID:         sdk.UUID(),
+		Type:       sdk.CDNTypeItemStepLog,
+		Status:     sdk.CDNStatusItemCompleted,
+		APIRefHash: sdk.RandomString(10),
+	}
+	require.NoError(t, item.Insert(context.TODO(), s.Mapper, db, &item1RedisCDS))
+	iu1CDS := sdk.CDNItemUnit{UnitID: s.Units.Storages[1].ID(), ItemID: item1RedisCDS.ID, Type: item1RedisCDS.Type}
+	require.NoError(t, storage.InsertItemUnit(context.TODO(), s.Mapper, db, &iu1CDS))
+	iu1Redis := sdk.CDNItemUnit{UnitID: s.Units.LogsBuffer().ID(), ItemID: item1RedisCDS.ID, Type: item1RedisCDS.Type}
+	require.NoError(t, storage.InsertItemUnit(context.TODO(), s.Mapper, db, &iu1Redis))
+
+	// Add Item in Redis and FS  -- Must be sync
+	item2RedisFs := sdk.CDNItem{
+		ID:         sdk.UUID(),
+		Type:       sdk.CDNTypeItemStepLog,
+		Status:     sdk.CDNStatusItemCompleted,
+		APIRefHash: sdk.RandomString(10),
+	}
+	require.NoError(t, item.Insert(context.TODO(), s.Mapper, db, &item2RedisFs))
+	iu2Redis := sdk.CDNItemUnit{UnitID: s.Units.LogsBuffer().ID(), ItemID: item2RedisFs.ID, Type: item2RedisFs.Type}
+	require.NoError(t, storage.InsertItemUnit(context.TODO(), s.Mapper, db, &iu2Redis))
+	iu2FS := sdk.CDNItemUnit{UnitID: s.Units.Storages[0].ID(), ItemID: item2RedisFs.ID, Type: item2RedisFs.Type}
+	require.NoError(t, storage.InsertItemUnit(context.TODO(), s.Mapper, db, &iu2FS))
+
+	// Add Item in redis only - have to stay in redis
+	item4Redis := sdk.CDNItem{
+		ID:         sdk.UUID(),
+		Type:       sdk.CDNTypeItemStepLog,
+		Status:     sdk.CDNStatusItemCompleted,
+		APIRefHash: sdk.RandomString(10),
+	}
+	require.NoError(t, item.Insert(context.TODO(), s.Mapper, db, &item4Redis))
+	iu4Redis := sdk.CDNItemUnit{UnitID: s.Units.LogsBuffer().ID(), ItemID: item4Redis.ID, Type: item4Redis.Type}
+	require.NoError(t, storage.InsertItemUnit(context.TODO(), s.Mapper, db, &iu4Redis))
+
+	// Add Item in redis / fs/ cds  -- Must be sync
+	item6RedisFSCDS := sdk.CDNItem{
+		ID:         sdk.UUID(),
+		Type:       sdk.CDNTypeItemStepLog,
+		Status:     sdk.CDNStatusItemCompleted,
+		APIRefHash: sdk.RandomString(10),
+	}
+	require.NoError(t, item.Insert(context.TODO(), s.Mapper, db, &item6RedisFSCDS))
+	iu6CDS := sdk.CDNItemUnit{UnitID: s.Units.Storages[1].ID(), ItemID: item6RedisFSCDS.ID, Type: item6RedisFSCDS.Type}
+	require.NoError(t, storage.InsertItemUnit(context.TODO(), s.Mapper, db, &iu6CDS))
+	iu6Redis := sdk.CDNItemUnit{UnitID: s.Units.LogsBuffer().ID(), ItemID: item6RedisFSCDS.ID, Type: item6RedisFSCDS.Type}
+	require.NoError(t, storage.InsertItemUnit(context.TODO(), s.Mapper, db, &iu6Redis))
+	iu6FS := sdk.CDNItemUnit{UnitID: s.Units.Storages[0].ID(), ItemID: item6RedisFSCDS.ID, Type: item6RedisFSCDS.Type}
+	require.NoError(t, storage.InsertItemUnit(context.TODO(), s.Mapper, db, &iu6FS))
+
+	oneHundred := 100
+
+	iusFS, err := storage.LoadItemUnitsByUnit(context.TODO(), s.Mapper, db, s.Units.Storages[0].ID(), &oneHundred)
+	require.NoError(t, err)
+	require.Equal(t, 2, len(iusFS))
+
+	iusCDS, err := storage.LoadItemUnitsByUnit(context.TODO(), s.Mapper, db, s.Units.Storages[1].ID(), &oneHundred)
+	require.NoError(t, err)
+	require.Equal(t, 2, len(iusCDS))
+
+	// RUN TEST
+	iusRedisBefore, err := storage.LoadItemUnitsByUnit(context.TODO(), s.Mapper, db, s.Units.LogsBuffer().ID(), &oneHundred)
+	require.NoError(t, err)
+	require.Equal(t, 4, len(iusRedisBefore))
+
+	require.NoError(t, s.cleanBuffer(context.TODO()))
+
+	iusRedisAfter, err := storage.LoadItemUnitsByUnit(context.TODO(), s.Mapper, db, s.Units.LogsBuffer().ID(), &oneHundred)
+	require.NoError(t, err)
+	require.Equal(t, 2, len(iusRedisAfter))
+
+	iusFS2After, err := storage.LoadItemUnitsByUnit(context.TODO(), s.Mapper, db, s.Units.Storages[0].ID(), &oneHundred)
+	require.NoError(t, err)
+	require.Equal(t, 2, len(iusFS2After))
+
+	iusCDSAfter, err := storage.LoadItemUnitsByUnit(context.TODO(), s.Mapper, db, s.Units.Storages[1].ID(), &oneHundred)
+	require.NoError(t, err)
+	require.Equal(t, 2, len(iusCDSAfter))
+}
+
 func TestCleanWaitingItem(t *testing.T) {
 	m := gorpmapper.New()
 	item.InitDBMapping(m)
diff --git a/engine/cdn/cdn_test.go b/engine/cdn/cdn_test.go
index d5243854e..df8e1ff6e 100644
--- a/engine/cdn/cdn_test.go
+++ b/engine/cdn/cdn_test.go
@@ -126,6 +126,8 @@ func newRunningStorageUnits(t *testing.T, m *gorpmapper.Mapper, dbMap *gorp.DbMa
 	cdnUnits, err := storage.Init(ctx, m, store, dbMap, sdk.NewGoRoutines(), storage.Configuration{
 		SyncSeconds:     2,
 		SyncNbElements:  100,
+		PurgeSeconds:    30,
+		PurgeNbElements: 100,
 		HashLocatorSalt: "thisismysalt",
 		Buffers: map[string]storage.BufferConfiguration{
 			"redis_buffer": {
diff --git a/engine/cdn/item/dao_test.go b/engine/cdn/item/dao_test.go
index 54ac64b82..6778ef280 100644
--- a/engine/cdn/item/dao_test.go
+++ b/engine/cdn/item/dao_test.go
@@ -42,6 +42,6 @@ func TestLoadItem(t *testing.T) {
 	_, has := res.GetCDNLogApiRef()
 	require.True(t, has)
 
-	_, no := res.APIRef.(*sdk.CDNArtifactAPIRef)
+	_, no := res.APIRef.(*sdk.CDNRunResultAPIRef)
 	require.False(t, no)
 }
diff --git a/engine/cdn/item_handler_test.go b/engine/cdn/item_handler_test.go
index 88f377e2d..85c4ffb0c 100644
--- a/engine/cdn/item_handler_test.go
+++ b/engine/cdn/item_handler_test.go
@@ -213,9 +213,9 @@ func TestGetItemArtifactDownloadHandler(t *testing.T) {
 	s.Client = cdsclient.New(cdsclient.Config{Host: "http://lolcat.api", InsecureSkipVerifyTLS: false})
 	gock.InterceptClient(s.Client.(cdsclient.Raw).HTTPClient())
 	t.Cleanup(gock.OffAll)
-	gock.New("http://lolcat.api").Get("/project/" + projectKey + "/workflows/WfName/type/artifact/access").Reply(http.StatusOK).JSON(nil)
+	gock.New("http://lolcat.api").Get("/project/" + projectKey + "/workflows/WfName/type/run-result/access").Reply(http.StatusOK).JSON(nil)
 
-	gock.New("http://lolcat.api").Post("/project/" + projectKey + "/workflows/WfName/runs/0/artifacts/check").Reply(http.StatusNoContent)
+	gock.New("http://lolcat.api").Post("/project/" + projectKey + "/workflows/WfName/runs/0/results/check").Reply(http.StatusNoContent)
 	gock.New("http://lolcat.api").Post("/project/" + projectKey + "/workflows/WfName/runs/0/results").Reply(http.StatusNoContent)
 
 	ctx, cancel := context.WithCancel(context.TODO())
@@ -241,8 +241,9 @@ func TestGetItemArtifactDownloadHandler(t *testing.T) {
 		JobID:        3,
 		JobName:      "JobDownload",
 		Worker: &cdn.SignatureWorker{
-			ArtifactName: "myartifact",
-			WorkerName:   "myworker",
+			FileName:      "myartifact",
+			WorkerName:    "myworker",
+			RunResultType: string(sdk.WorkflowRunResultTypeArtifact),
 		},
 	}
 
@@ -265,12 +266,12 @@ func TestGetItemArtifactDownloadHandler(t *testing.T) {
 	jwtTokenRaw, err := signer.SignJWT(jwtToken)
 	require.NoError(t, err)
 
-	apiRef := sdk.NewCDNArtifactApiRef(sig)
+	apiRef := sdk.NewCDNRunResultApiRef(sig)
 	refhash, err := apiRef.ToHash()
 	require.NoError(t, err)
 
 	uri := s.Router.GetRoute("GET", s.getItemDownloadHandler, map[string]string{
-		"type":   string(sdk.CDNTypeItemArtifact),
+		"type":   string(sdk.CDNTypeItemRunResult),
 		"apiRef": refhash,
 	})
 	require.NotEmpty(t, uri)
@@ -318,7 +319,7 @@ func TestGetItemArtifactDownloadHandler(t *testing.T) {
 
 	// Download again
 	uri = s.Router.GetRoute("GET", s.getItemDownloadHandler, map[string]string{
-		"type":   string(sdk.CDNTypeItemArtifact),
+		"type":   string(sdk.CDNTypeItemRunResult),
 		"apiRef": refhash,
 	})
 	require.NotEmpty(t, uri)
@@ -350,16 +351,17 @@ func TestGetItemsArtefactHandler(t *testing.T) {
 		RunID:        1,
 		WorkflowName: "my workflow",
 		Worker: &cdn.SignatureWorker{
-			WorkerID:     "1",
-			WorkerName:   "workername",
-			ArtifactName: "myartifact",
+			WorkerID:      "1",
+			WorkerName:    "workername",
+			FileName:      "myartifact",
+			RunResultType: string(sdk.WorkflowRunResultTypeArtifact),
 		},
 	}
 
 	item1 := sdk.CDNItem{
-		Type:   sdk.CDNTypeItemArtifact,
+		Type:   sdk.CDNTypeItemRunResult,
 		Status: sdk.CDNStatusItemCompleted,
-		APIRef: sdk.NewCDNArtifactApiRef(workerSignature),
+		APIRef: sdk.NewCDNRunResultApiRef(workerSignature),
 	}
 	refhash, err := item1.APIRef.ToHash()
 	require.NoError(t, err)
@@ -377,7 +379,7 @@ func TestGetItemsArtefactHandler(t *testing.T) {
 	require.NoError(t, item.Insert(context.Background(), s.Mapper, db, &item2))
 
 	vars := map[string]string{
-		"type": string(sdk.CDNTypeItemArtifact),
+		"type": string(sdk.CDNTypeItemRunResult),
 	}
 	uri := s.Router.GetRoute("GET", s.getItemsHandler, vars)
 	require.NotEmpty(t, uri)
diff --git a/engine/cdn/item_upload_test.go b/engine/cdn/item_upload_test.go
index aa3ddd1c0..de25418d9 100644
--- a/engine/cdn/item_upload_test.go
+++ b/engine/cdn/item_upload_test.go
@@ -56,6 +56,8 @@ func TestPostUploadHandler(t *testing.T) {
 	cdnUnits, err := storage.Init(ctx, s.Mapper, s.Cache, db.DbMap, sdk.NewGoRoutines(), storage.Configuration{
 		SyncSeconds:     1,
 		SyncNbElements:  1000,
+		PurgeNbElements: 1000,
+		PurgeSeconds:    30,
 		HashLocatorSalt: "thisismysalt",
 		Buffers: map[string]storage.BufferConfiguration{
 			"refis_buffer": {
@@ -116,7 +118,7 @@ func TestPostUploadHandler(t *testing.T) {
 		PrivateKey: []byte(base64.StdEncoding.EncodeToString(workerKey)),
 	}
 
-	gock.New("http://lolcat.api").Post("/project/projKey/workflows/myworkflow/runs/0/artifacts/check").Reply(http.StatusNoContent)
+	gock.New("http://lolcat.api").Post("/project/projKey/workflows/myworkflow/runs/0/results/check").Reply(http.StatusNoContent)
 	gock.New("http://lolcat.api").Post("/project/projKey/workflows/myworkflow/runs/0/results").Reply(http.StatusNoContent)
 	gock.New("http://lolcat.api").Get("/worker/myworker").MatchParam("withKey", "true").Reply(200).JSON(worker)
 
@@ -129,9 +131,10 @@ func TestPostUploadHandler(t *testing.T) {
 		RunID:        1,
 		WorkflowName: "myworkflow",
 		Worker: &cdn.SignatureWorker{
-			WorkerID:     worker.ID,
-			WorkerName:   worker.Name,
-			ArtifactName: "myartifact",
+			WorkerID:      worker.ID,
+			WorkerName:    worker.Name,
+			FileName:      "myartifact",
+			RunResultType: string(sdk.WorkflowRunResultTypeArtifact),
 		},
 	}
 	signer, err := jws.NewHMacSigner(workerKey)
diff --git a/engine/cdn/storage/nfs/nfs_test.go b/engine/cdn/storage/nfs/nfs_test.go
index 6ac029587..898fd2b63 100644
--- a/engine/cdn/storage/nfs/nfs_test.go
+++ b/engine/cdn/storage/nfs/nfs_test.go
@@ -80,10 +80,10 @@ func TestNFSReadWrite(t *testing.T) {
 	}, storage.CDNBufferTypeFile)
 	require.NoError(t, err, "unable to initialiaze nfs driver")
 	itemUnit := sdk.CDNItemUnit{
-		Type:    sdk.CDNTypeItemArtifact,
+		Type:    sdk.CDNTypeItemRunResult,
 		Locator: sdk.RandomString(10),
 		Item: &sdk.CDNItem{
-			Type:       sdk.CDNTypeItemArtifact,
+			Type:       sdk.CDNTypeItemRunResult,
 			APIRefHash: sdk.RandomString(10),
 		},
 	}
diff --git a/engine/cdn/unit_handler_test.go b/engine/cdn/unit_handler_test.go
index 0470ffa0a..4b49cfef2 100644
--- a/engine/cdn/unit_handler_test.go
+++ b/engine/cdn/unit_handler_test.go
@@ -72,7 +72,7 @@ func TestMarkItemUnitAsDeleteHandler(t *testing.T) {
 		if cpt >= 10 {
 			t.FailNow()
 		}
-		uis, err := storage.LoadAllItemUnitsToDeleteByUnit(ctx, s.Mapper, db, unit.ID)
+		uis, err := storage.LoadAllItemUnitsToDeleteByUnit(ctx, s.Mapper, db, unit.ID, 100)
 		require.NoError(t, err)
 		if len(uis) != 10 {
 			time.Sleep(250 * time.Millisecond)
diff --git a/engine/worker/internal/action/test_helper.go b/engine/worker/internal/action/test_helper.go
index 967cc3bdb..98c394f84 100644
--- a/engine/worker/internal/action/test_helper.go
+++ b/engine/worker/internal/action/test_helper.go
@@ -34,7 +34,7 @@ func (w *TestWorker) WorkerCacheSignature(tag string) (string, error) {
 	return "mysig", nil
 }
 
-func (w *TestWorker) ArtifactSignature(artifactName string, perm uint32) (string, error) {
+func (w *TestWorker) RunResultSignature(artifactName string, perm uint32, t sdk.WorkflowRunResultType) (string, error) {
 	return "mysignature", nil
 }
 
diff --git a/sdk/cdn_test.go b/sdk/cdn_test.go
index cb605f6c9..a4fddaf94 100644
--- a/sdk/cdn_test.go
+++ b/sdk/cdn_test.go
@@ -41,10 +41,10 @@ func TestCDNItemLogJSON(t *testing.T) {
 
 func TestCDNItemArtefactJSON(t *testing.T) {
 	item := CDNItem{
-		APIRef: NewCDNArtifactApiRef(cdn.Signature{
+		APIRef: NewCDNRunResultApiRef(cdn.Signature{
 			Worker: &cdn.SignatureWorker{
-				WorkerName:   "workername",
-				ArtifactName: "myartifact",
+				WorkerName: "workername",
+				FileName:   "myartifact",
 			},
 			ProjectKey:   "KEY",
 			WorkflowName: "NAME",
@@ -54,7 +54,7 @@ func TestCDNItemArtefactJSON(t *testing.T) {
 			WorkflowID:   1,
 		}),
 		ID:   "AAA",
-		Type: CDNTypeItemArtifact,
+		Type: CDNTypeItemRunResult,
 	}
 	bts, err := json.Marshal(item)
 	require.NoError(t, err)
@@ -62,7 +62,7 @@ func TestCDNItemArtefactJSON(t *testing.T) {
 	var itemU CDNItem
 	require.NoError(t, json.Unmarshal(bts, &itemU))
 
-	_, is := itemU.GetCDNArtifactApiRef()
+	_, is := itemU.GetCDNRunResultApiRef()
 	require.True(t, is)
 	require.Equal(t, "myartifact", itemU.APIRef.ToFilename())
 }