@@ -339,6 +339,58 @@ describe('applySync', () => {
339339 } ) ) ;
340340 } ) ;
341341
342+ it ( 'creates missing control row before reconciling RequirementMap drift when v1 and v2 both claim it' , async ( ) => {
343+ const tx = mockTx ( ) ;
344+ tx . control . create . mockResolvedValue ( {
345+ id : 'ctl_created' ,
346+ controlTemplateId : 'ct_1' ,
347+ organizationId : 'org_1' ,
348+ name : 'C' ,
349+ description : 'D' ,
350+ archivedAt : null ,
351+ } ) ;
352+ tx . control . findMany . mockResolvedValue ( [ ] ) ;
353+ tx . requirementMap . findMany . mockResolvedValue ( [ ] ) ;
354+
355+ const sameControl = { id : 'ct_1' , name : 'C' , description : 'D' , requirementIds : [ 'rq_1' ] , policyIds : [ ] , taskIds : [ ] } ;
356+ await applySync ( tx , {
357+ instance : baseInstance as any ,
358+ currentVersion : {
359+ id : 'fvr_v1' ,
360+ frameworkId : 'frk_soc2' ,
361+ manifest : manifest ( {
362+ requirements : [ { id : 'rq_1' , identifier : 'CC1' , name : 'X' , description : null } ] ,
363+ controls : [ sameControl ] ,
364+ } ) ,
365+ } as any ,
366+ targetVersion : {
367+ id : 'fvr_v2' ,
368+ frameworkId : 'frk_soc2' ,
369+ manifest : manifest ( {
370+ requirements : [ { id : 'rq_1' , identifier : 'CC1' , name : 'X' , description : null } ] ,
371+ controls : [ sameControl ] ,
372+ } ) ,
373+ } as any ,
374+ memberId : 'mem_1' ,
375+ } ) ;
376+
377+ expect ( tx . control . create ) . toHaveBeenCalledWith ( expect . objectContaining ( {
378+ data : expect . objectContaining ( {
379+ organizationId : 'org_1' ,
380+ controlTemplateId : 'ct_1' ,
381+ name : 'C' ,
382+ description : 'D' ,
383+ } ) ,
384+ } ) ) ;
385+ expect ( tx . requirementMap . create ) . toHaveBeenCalledWith ( expect . objectContaining ( {
386+ data : expect . objectContaining ( {
387+ controlId : 'ctl_created' ,
388+ requirementId : 'rq_1' ,
389+ frameworkInstanceId : 'frm_1' ,
390+ } ) ,
391+ } ) ) ;
392+ } ) ;
393+
342394 it ( 'unarchives an existing archived RequirementMap row instead of creating a duplicate' , async ( ) => {
343395 const tx = mockTx ( ) ;
344396 tx . control . findMany . mockResolvedValue ( [
@@ -414,6 +466,54 @@ describe('applySync', () => {
414466 expect ( cpInsertCalled ) . toBe ( true ) ;
415467 } ) ;
416468
469+ it ( 'creates missing task row before reconciling _ControlToTask drift when v1 and v2 both claim it' , async ( ) => {
470+ const tx = mockTx ( ) ;
471+ tx . control . findMany . mockResolvedValue ( [
472+ { id : 'ctl_1' , controlTemplateId : 'ct_1' , organizationId : 'org_1' , name : 'C' , description : 'D' , archivedAt : null } ,
473+ ] ) ;
474+ tx . task . findMany . mockResolvedValue ( [ ] ) ;
475+ tx . task . create . mockResolvedValue ( {
476+ id : 'tsk_created' ,
477+ taskTemplateId : 'tt_1' ,
478+ organizationId : 'org_1' ,
479+ title : 'T' ,
480+ description : 'D' ,
481+ frequency : null ,
482+ department : null ,
483+ archivedAt : null ,
484+ } ) ;
485+ tx . $queryRaw . mockResolvedValue ( [ ] ) ;
486+
487+ const sameControl = { id : 'ct_1' , name : 'C' , description : 'D' , requirementIds : [ ] , policyIds : [ ] , taskIds : [ 'tt_1' ] } ;
488+ const sameTask = { id : 'tt_1' , name : 'T' , description : 'D' , frequency : null , department : null } ;
489+ await applySync ( tx , {
490+ instance : baseInstance as any ,
491+ currentVersion : {
492+ id : 'fvr_v1' ,
493+ frameworkId : 'frk_soc2' ,
494+ manifest : manifest ( { controls : [ sameControl ] , tasks : [ sameTask ] } ) ,
495+ } as any ,
496+ targetVersion : {
497+ id : 'fvr_v2' ,
498+ frameworkId : 'frk_soc2' ,
499+ manifest : manifest ( { controls : [ sameControl ] , tasks : [ sameTask ] } ) ,
500+ } as any ,
501+ memberId : 'mem_1' ,
502+ } ) ;
503+
504+ expect ( tx . task . create ) . toHaveBeenCalledWith ( expect . objectContaining ( {
505+ data : expect . objectContaining ( {
506+ organizationId : 'org_1' ,
507+ taskTemplateId : 'tt_1' ,
508+ title : 'T' ,
509+ description : 'D' ,
510+ } ) ,
511+ } ) ) ;
512+ const calls = tx . $executeRaw . mock . calls . map ( ( c : unknown [ ] ) => String ( c [ 0 ] ?. [ 0 ] ?? '' ) ) ;
513+ const ctInsertCalled = calls . some ( ( s : string ) => s . includes ( 'INSERT INTO "_ControlToTask"' ) ) ;
514+ expect ( ctInsertCalled ) . toBe ( true ) ;
515+ } ) ;
516+
417517 it ( 'creates missing ControlDocumentType row when v1 and v2 both claim it but customer has no row (drift)' , async ( ) => {
418518 const tx = mockTx ( ) ;
419519 tx . control . findMany . mockResolvedValue ( [
0 commit comments