55 NotFoundException ,
66} from '@nestjs/common' ;
77import { db , type EvidenceFormType } from '@db' ;
8+
9+ import { tasks } from '@trigger.dev/sdk' ;
810import {
911 getOverviewScores ,
1012 getCurrentMember ,
@@ -15,6 +17,7 @@ import { createTimelinesForFrameworks } from './frameworks-timeline.helper';
1517import { TimelinesService } from '../timelines/timelines.service' ;
1618import type { FrameworkManifest } from './framework-versioning/manifest.types' ;
1719import { buildUpdatePreview } from './framework-versioning/framework-update-preview' ;
20+ import type { updatePolicy } from '../trigger/policies/update-policy' ;
1821
1922type RequirementDef = {
2023 id : string ;
@@ -543,7 +546,11 @@ export class FrameworksService {
543546 return { ...scores , currentMember } ;
544547 }
545548
546- async addFrameworks ( organizationId : string , frameworkIds : string [ ] ) {
549+ async addFrameworks (
550+ organizationId : string ,
551+ frameworkIds : string [ ] ,
552+ memberId ?: string ,
553+ ) {
547554 const result = await db . $transaction ( async ( tx ) => {
548555 const frameworks = await tx . frameworkEditorFramework . findMany ( {
549556 where : { id : { in : frameworkIds } , visible : true } ,
@@ -558,14 +565,19 @@ export class FrameworksService {
558565
559566 const finalIds = frameworks . map ( ( f ) => f . id ) ;
560567
561- await upsertOrgFrameworkStructure ( {
568+ const upsertResult = await upsertOrgFrameworkStructure ( {
562569 organizationId,
563570 targetFrameworkEditorIds : finalIds ,
564571 frameworkEditorFrameworks : frameworks ,
565572 tx,
566573 } ) ;
567574
568- return { success : true , frameworksAdded : finalIds . length , finalIds } ;
575+ return {
576+ success : true ,
577+ frameworksAdded : finalIds . length ,
578+ finalIds,
579+ createdPolicyIds : upsertResult . createdPolicyIds ,
580+ } ;
569581 } ) ;
570582
571583 // Auto-create timeline instances from templates for newly added
@@ -584,9 +596,102 @@ export class FrameworksService {
584596 ) ;
585597 } ) ;
586598
599+ // Regenerate only newly created policies so placeholder replacement runs
600+ // without touching customer-edited existing policies.
601+ if ( result . createdPolicyIds . length > 0 ) {
602+ this . enqueuePolicyGenerationForNewPolicies ( {
603+ organizationId,
604+ policyIds : result . createdPolicyIds ,
605+ memberId,
606+ } ) . catch ( ( err ) => {
607+ this . logger . warn (
608+ 'enqueuePolicyGenerationForNewPolicies failed after framework add' ,
609+ err ,
610+ ) ;
611+ } ) ;
612+ }
613+
587614 return { success : result . success , frameworksAdded : result . frameworksAdded } ;
588615 }
589616
617+ private async enqueuePolicyGenerationForNewPolicies ( {
618+ organizationId,
619+ policyIds,
620+ memberId,
621+ } : {
622+ organizationId : string ;
623+ policyIds : string [ ] ;
624+ memberId ?: string ;
625+ } ) {
626+ const [ instances , contextEntries ] = await Promise . all ( [
627+ db . frameworkInstance . findMany ( {
628+ where : { organizationId } ,
629+ include : { framework : true , customFramework : true } ,
630+ } ) ,
631+ db . context . findMany ( {
632+ where : { organizationId } ,
633+ orderBy : { createdAt : 'asc' } ,
634+ } ) ,
635+ ] ) ;
636+
637+ const normalized = instances . map ( ( fi ) => {
638+ if ( fi . framework ) {
639+ return {
640+ id : fi . framework . id ,
641+ name : fi . framework . name ,
642+ version : fi . framework . version ,
643+ description : fi . framework . description ,
644+ visible : fi . framework . visible ,
645+ createdAt : fi . framework . createdAt ,
646+ updatedAt : fi . framework . updatedAt ,
647+ } ;
648+ }
649+ if ( fi . customFramework ) {
650+ return {
651+ id : fi . customFramework . id ,
652+ name : fi . customFramework . name ,
653+ version : fi . customFramework . version ,
654+ description : fi . customFramework . description ,
655+ visible : true ,
656+ createdAt : fi . customFramework . createdAt ,
657+ updatedAt : fi . customFramework . updatedAt ,
658+ } ;
659+ }
660+ return null ;
661+ } ) ;
662+ const uniqueFrameworks = Array . from (
663+ new Map (
664+ normalized
665+ . filter ( ( f ) : f is NonNullable < typeof f > => f !== null )
666+ . map ( ( f ) => [ f . id , f ] ) ,
667+ ) . values ( ) ,
668+ ) ;
669+
670+ const contextHub = contextEntries
671+ . map ( ( c ) => `${ c . question } \n${ c . answer } ` )
672+ . join ( '\n' ) ;
673+
674+ const triggerResults = await Promise . allSettled (
675+ policyIds . map ( ( policyId ) =>
676+ tasks . trigger < typeof updatePolicy > ( 'update-policy' , {
677+ organizationId,
678+ policyId,
679+ contextHub,
680+ frameworks : uniqueFrameworks ,
681+ memberId,
682+ } ) ,
683+ ) ,
684+ ) ;
685+
686+ const failedTrigger = triggerResults . find (
687+ ( result ) : result is PromiseRejectedResult => result . status === 'rejected' ,
688+ ) ;
689+ if ( failedTrigger ) {
690+ this . logger . error ( 'Failed to trigger policy update' , failedTrigger . reason ) ;
691+ throw new Error ( 'Failed to trigger policy update' ) ;
692+ }
693+ }
694+
590695 async findRequirement (
591696 frameworkInstanceId : string ,
592697 requirementKey : string ,
0 commit comments