@@ -792,8 +792,11 @@ export async function deleteFolder(
792792 *
793793 * `notify: false` for a caller deleting several folders in one gesture that
794794 * sends a single batch notification of its own — see {@link bulkDeleteFolders}.
795+ * `referenceCheckCompleted: true` reuses that batch's reference scan; the
796+ * table guard still checks deletion locks, and the table service rechecks
797+ * references under its own transaction locks before archiving the cohort.
795798 */
796- options ?: { projectAudit ?: boolean ; notify ?: boolean }
799+ options ?: { projectAudit ?: boolean ; notify ?: boolean ; referenceCheckCompleted ?: boolean }
797800) : Promise < DeleteFolderResult > {
798801 const existing = await withFolderTreeLock ( params . workspaceId , params . resourceType , async ( tx ) => {
799802 const [ row ] = await tx
@@ -817,13 +820,14 @@ export async function deleteFolder(
817820 return deleteFolderWithoutTreeLock ( params , existing . deletedAt , {
818821 projectAudit : options ?. projectAudit ?? true ,
819822 notify : options ?. notify ?? true ,
823+ referenceCheckCompleted : options ?. referenceCheckCompleted ,
820824 } )
821825}
822826
823827async function deleteFolderWithoutTreeLock (
824828 params : DeleteFolderParams ,
825829 deletedAt : Date | null ,
826- options : { projectAudit : boolean ; notify : boolean }
830+ options : { projectAudit : boolean ; notify : boolean ; referenceCheckCompleted ?: boolean }
827831) : Promise < DeleteFolderResult > {
828832 const { resourceType, folderId, workspaceId, userId, folderName, folderPath } = params
829833 const config = folderResourceConfig ( resourceType )
@@ -844,12 +848,25 @@ async function deleteFolderWithoutTreeLock(
844848 params . maxFolderRows
845849 )
846850
847- const rejection = await config . guardDelete ?.( { workspaceId, folderIds } )
851+ const rejection = await config . guardDelete ?.( {
852+ workspaceId,
853+ folderIds,
854+ ...( options . referenceCheckCompleted ? { referenceCheckCompleted : true } : { } ) ,
855+ } )
848856 if ( rejection ) {
849857 return { success : false , error : rejection . error , errorCode : rejection . errorCode }
850858 }
851859
852- const counts = await archiveFolderCascade ( db , config , workspaceId , folderIds , timestamp )
860+ let counts : Awaited < ReturnType < typeof archiveFolderCascade > >
861+ try {
862+ counts = await archiveFolderCascade ( db , config , workspaceId , folderIds , timestamp )
863+ } catch ( error ) {
864+ const classified = asOrchestrationError ( error )
865+ if ( classified ?. code === 'locked' || classified ?. code === 'conflict' ) {
866+ return { success : false , error : classified . message , errorCode : classified . code }
867+ }
868+ throw error
869+ }
853870
854871 logger . info ( 'Deleted folder and all contents' , { folderId, resourceType, counts } )
855872
0 commit comments