diff --git a/.yarn/install-state.gz b/.yarn/install-state.gz index ee729a0d8b..c7e4a26324 100644 Binary files a/.yarn/install-state.gz and b/.yarn/install-state.gz differ diff --git a/server/modules/nodeDef/manager/nodeDefManager.js b/server/modules/nodeDef/manager/nodeDefManager.js index 77d17112b5..26032c3a1e 100644 --- a/server/modules/nodeDef/manager/nodeDefManager.js +++ b/server/modules/nodeDef/manager/nodeDefManager.js @@ -76,6 +76,10 @@ const _onAncestorCyclesUpdate = async ({ survey, nodeDefAncestor, cycles, cycles // perform updates in batch await client.batch(batchUpdates) + // a descendant left with no cycles is a dangling node def (invisible in every cycle, but still + // occupying its name); soft-delete it instead of leaving it as a live orphan + await NodeDefRepository.markNodeDefsWithoutCyclesDeleted(surveyId, client) + return nodeDefsUpdated } diff --git a/server/modules/survey/service/publish/jobs/nodeDefsValidationJob.js b/server/modules/survey/service/publish/jobs/nodeDefsValidationJob.js index 2d1a1b8df0..0647af6639 100644 --- a/server/modules/survey/service/publish/jobs/nodeDefsValidationJob.js +++ b/server/modules/survey/service/publish/jobs/nodeDefsValidationJob.js @@ -38,6 +38,10 @@ export default class NodeDefsValidationJob extends Job { this.setContext({ surveyPublishedPrevious }) await NodeDefManager.deleteOrphaneNodeDefs(surveyId, tx) + // self-heal node defs left with an empty cycles array by past bugs (e.g. narrowing an + // ancestor entity's cycles), which would otherwise remain invisible ghosts blocking publish + // with false duplicate-name errors + await NodeDefManager.markNodeDefsWithoutCyclesDeleted(surveyId, tx) const surveySummary = await SurveyManager.fetchSurveyById({ surveyId, draft: true }, tx) const cycleKeys = R.pipe(Survey.getSurveyInfo, Survey.getCycleKeys)(surveySummary)