From 784ca0741942677264d8a7b352fba0d43f5b99fe Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Charles-Edouard=20Br=C3=A9t=C3=A9ch=C3=A9?=
 <charles.edouard@nirmata.com>
Date: Wed, 5 Apr 2023 12:35:38 +0200
Subject: [PATCH] refactor: engine rule response creation (#6784)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* refactor: engine rule response creation

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* private fields

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* more private

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* more more private

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* more private

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* more private

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* more private

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* more private

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* more private

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* more private

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* more private

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* more private

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* more private

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix unit tests

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

---------

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
---
 cmd/cli/kubectl-kyverno/apply/report_test.go  |  31 +-
 cmd/cli/kubectl-kyverno/test/test_command.go  |  24 +-
 .../kubectl-kyverno/utils/common/common.go    |  39 +--
 pkg/background/generate/generate.go           |   4 +-
 pkg/background/mutate/mutate.go               |  25 +-
 pkg/controllers/report/utils/events.go        |   5 +-
 pkg/engine/api/engineresponse.go              |   8 +-
 pkg/engine/api/engineresponse_test.go         | 322 ++++++++----------
 pkg/engine/api/policyresponse.go              |  11 +-
 pkg/engine/api/ruleresponse.go                | 166 +++++++--
 pkg/engine/api/ruleresponse_test.go           |  71 ++--
 pkg/engine/background.go                      |  26 +-
 pkg/engine/engine.go                          |  12 +-
 pkg/engine/exceptions.go                      |  28 +-
 pkg/engine/forceMutate.go                     |   4 +-
 pkg/engine/handlers/handler.go                |  18 +-
 pkg/engine/handlers/mutation/common.go        |  22 +-
 .../handlers/mutation/mutate_existing.go      |   8 +-
 pkg/engine/handlers/mutation/mutate_image.go  |   6 +-
 .../handlers/mutation/mutate_resource.go      |   2 +-
 .../handlers/validation/validate_image.go     |   5 +-
 .../handlers/validation/validate_manifest.go  |   6 +-
 .../handlers/validation/validate_pss.go       |  19 +-
 .../handlers/validation/validate_resource.go  |  66 ++--
 pkg/engine/image_verify.go                    |   5 +-
 pkg/engine/image_verify_test.go               |  42 +--
 pkg/engine/internal/imageverifier.go          |  30 +-
 pkg/engine/internal/response.go               |  56 ---
 pkg/engine/metrics.go                         |   6 +-
 pkg/engine/mutate/mutation.go                 |  16 +-
 pkg/engine/mutate/mutation_test.go            |  54 ++-
 pkg/engine/mutate/patch/patchJSON6902.go      |  40 +--
 pkg/engine/mutate/patch/patchJSON6902_test.go |   8 +-
 pkg/engine/mutate/patch/patches.go            |  14 +-
 .../mutate/patch/strategicMergePatch.go       |  34 +-
 pkg/engine/mutation.go                        |   5 +-
 pkg/engine/mutation_test.go                   |  60 ++--
 pkg/engine/validation.go                      |   5 +-
 pkg/engine/validation_test.go                 |  68 ++--
 pkg/event/events.go                           |  17 +-
 pkg/metrics/parsers.go                        |   2 +-
 pkg/policy/policy_controller.go               |   4 +-
 pkg/utils/annotations.go                      |   4 +-
 pkg/utils/annotations_test.go                 |   6 +-
 pkg/utils/report/results.go                   |  15 +-
 pkg/webhooks/resource/generation/handler.go   |   6 +-
 pkg/webhooks/resource/updaterequest.go        |   2 +-
 pkg/webhooks/resource/validation_test.go      |   4 +-
 pkg/webhooks/utils/block.go                   |   6 +-
 pkg/webhooks/utils/block_test.go              |  60 +---
 pkg/webhooks/utils/error.go                   |   2 +-
 pkg/webhooks/utils/event.go                   |   5 +-
 pkg/webhooks/utils/warning.go                 |   4 +-
 pkg/webhooks/utils/warning_test.go            |  36 +-
 54 files changed, 718 insertions(+), 826 deletions(-)
 delete mode 100644 pkg/engine/internal/response.go

diff --git a/cmd/cli/kubectl-kyverno/apply/report_test.go b/cmd/cli/kubectl-kyverno/apply/report_test.go
index fba892c16c..818fdbe66b 100644
--- a/cmd/cli/kubectl-kyverno/apply/report_test.go
+++ b/cmd/cli/kubectl-kyverno/apply/report_test.go
@@ -3,6 +3,7 @@ package apply
 import (
 	"encoding/json"
 	"testing"
+	"time"
 
 	kyverno "github.com/kyverno/kyverno/api/kyverno/v1"
 	preport "github.com/kyverno/kyverno/api/policyreport/v1alpha2"
@@ -83,8 +84,6 @@ var rawPolicy = []byte(`
   }
 `)
 
-var rawEngRes = []byte(`{"PatchedResource":{"apiVersion":"v1","kind":"Pod","metadata":{"name":"nginx1","namespace":"default"},"spec":{"containers":[{"image":"nginx","imagePullPolicy":"IfNotPresent","name":"nginx","resources":{"limits":{"cpu":"200m","memory":"100Mi"},"requests":{"cpu":"100m","memory":"50Mi"}}}]}},"PolicyResponse":{"policy":{"name":"pod-requirements","namespace":""},"resource":{"kind":"Pod","apiVersion":"v1","namespace":"default","name":"nginx1","uid":""},"processingTime":974958,"rulesAppliedCount":2,"policyExecutionTimestamp":1630527712,"rules":[{"name":"pods-require-account","type":"Validation","message":"validation error: User pods must include an account for charging. Rule pods-require-account failed at path /metadata/labels/","status":"fail","processingTime":28833,"ruleExecutionTimestamp":1630527712},{"name":"pods-require-limits","type":"Validation","message":"validation rule 'pods-require-limits' passed.","status":"pass","processingTime":578625,"ruleExecutionTimestamp":1630527712}],"ValidationFailureAction":"audit"}}`)
-
 func Test_buildPolicyReports(t *testing.T) {
 	rc := &kyvCommon.ResultCounts{}
 	var pvInfos []common.Info
@@ -92,10 +91,18 @@ func Test_buildPolicyReports(t *testing.T) {
 	err := json.Unmarshal(rawPolicy, &policy)
 	assert.NilError(t, err)
 
-	var er engineapi.EngineResponse
-	err = json.Unmarshal(rawEngRes, &er)
+	er := engineapi.EngineResponse{}
 	er.Policy = &policy
-	assert.NilError(t, err)
+	er.PolicyResponse.Add(time.Now(), time.Now(), *engineapi.RuleFail(
+		"pods-require-account",
+		engineapi.Validation,
+		"validation error: User pods must include an account for charging. Rule pods-require-account failed at path /metadata/labels/"),
+	)
+	er.PolicyResponse.Add(time.Now(), time.Now(), *engineapi.RulePass(
+		"pods-require-limits",
+		engineapi.Validation,
+		"validation rule 'pods-require-limits' passed."),
+	)
 
 	info := kyvCommon.ProcessValidateEngineResponse(&policy, &er, "", rc, true, false)
 	pvInfos = append(pvInfos, info)
@@ -129,10 +136,18 @@ func Test_buildPolicyResults(t *testing.T) {
 	err := json.Unmarshal(rawPolicy, &policy)
 	assert.NilError(t, err)
 
-	var er engineapi.EngineResponse
-	err = json.Unmarshal(rawEngRes, &er)
+	er := engineapi.EngineResponse{}
 	er.Policy = &policy
-	assert.NilError(t, err)
+	er.PolicyResponse.Add(time.Now(), time.Now(), *engineapi.RuleFail(
+		"pods-require-account",
+		engineapi.Validation,
+		"validation error: User pods must include an account for charging. Rule pods-require-account failed at path /metadata/labels/"),
+	)
+	er.PolicyResponse.Add(time.Now(), time.Now(), *engineapi.RulePass(
+		"pods-require-limits",
+		engineapi.Validation,
+		"validation rule 'pods-require-limits' passed."),
+	)
 
 	info := kyvCommon.ProcessValidateEngineResponse(&policy, &er, "", rc, true, false)
 	pvInfos = append(pvInfos, info)
diff --git a/cmd/cli/kubectl-kyverno/test/test_command.go b/cmd/cli/kubectl-kyverno/test/test_command.go
index ef1508d540..c4d019c8bc 100644
--- a/cmd/cli/kubectl-kyverno/test/test_command.go
+++ b/cmd/cli/kubectl-kyverno/test/test_command.go
@@ -440,7 +440,7 @@ func buildPolicyResults(engineResponses []*engineapi.EngineResponse, testResults
 
 		var rules []string
 		for _, rule := range resp.PolicyResponse.Rules {
-			rules = append(rules, rule.Name)
+			rules = append(rules, rule.Name())
 		}
 
 		result := policyreportv1alpha2.PolicyReportResult{
@@ -542,14 +542,14 @@ func buildPolicyResults(engineResponses []*engineapi.EngineResponse, testResults
 			}
 
 			for _, rule := range resp.PolicyResponse.Rules {
-				if rule.Type != engineapi.Generation || test.Rule != rule.Name {
+				if rule.RuleType() != engineapi.Generation || test.Rule != rule.Name() {
 					continue
 				}
 
 				var resultsKey []string
 				var resultKey string
 				var result policyreportv1alpha2.PolicyReportResult
-				resultsKey = GetAllPossibleResultsKey(policyNamespace, policyName, rule.Name, resourceNamespace, resourceKind, resourceName)
+				resultsKey = GetAllPossibleResultsKey(policyNamespace, policyName, rule.Name(), resourceNamespace, resourceKind, resourceName)
 				for _, key := range resultsKey {
 					if val, ok := results[key]; ok {
 						result = val
@@ -558,14 +558,14 @@ func buildPolicyResults(engineResponses []*engineapi.EngineResponse, testResults
 						continue
 					}
 
-					if rule.Status == engineapi.RuleStatusSkip {
+					if rule.Status() == engineapi.RuleStatusSkip {
 						result.Result = policyreportv1alpha2.StatusSkip
-					} else if rule.Status == engineapi.RuleStatusError {
+					} else if rule.Status() == engineapi.RuleStatusError {
 						result.Result = policyreportv1alpha2.StatusError
 					} else {
 						var x string
 						result.Result = policyreportv1alpha2.StatusFail
-						x = getAndCompareResource(test.GeneratedResource, rule.GeneratedResource, isGit, policyResourcePath, fs, true)
+						x = getAndCompareResource(test.GeneratedResource, rule.GeneratedResource(), isGit, policyResourcePath, fs, true)
 						if x == "pass" {
 							result.Result = policyreportv1alpha2.StatusPass
 						}
@@ -576,14 +576,14 @@ func buildPolicyResults(engineResponses []*engineapi.EngineResponse, testResults
 		}
 
 		for _, rule := range resp.PolicyResponse.Rules {
-			if rule.Type != engineapi.Mutation {
+			if rule.RuleType() != engineapi.Mutation {
 				continue
 			}
 
 			var resultsKey []string
 			var resultKey string
 			var result policyreportv1alpha2.PolicyReportResult
-			resultsKey = GetAllPossibleResultsKey(policyNamespace, policyName, rule.Name, resourceNamespace, resourceKind, resourceName)
+			resultsKey = GetAllPossibleResultsKey(policyNamespace, policyName, rule.Name(), resourceNamespace, resourceKind, resourceName)
 			for _, key := range resultsKey {
 				if val, ok := results[key]; ok {
 					result = val
@@ -592,9 +592,9 @@ func buildPolicyResults(engineResponses []*engineapi.EngineResponse, testResults
 					continue
 				}
 
-				if rule.Status == engineapi.RuleStatusSkip {
+				if rule.Status() == engineapi.RuleStatusSkip {
 					result.Result = policyreportv1alpha2.StatusSkip
-				} else if rule.Status == engineapi.RuleStatusError {
+				} else if rule.Status() == engineapi.RuleStatusError {
 					result.Result = policyreportv1alpha2.StatusError
 				} else {
 					var x string
@@ -710,8 +710,8 @@ func getAndCompareResource(path string, engineResource unstructured.Unstructured
 func buildMessage(resp *engineapi.EngineResponse) string {
 	var bldr strings.Builder
 	for _, ruleResp := range resp.PolicyResponse.Rules {
-		fmt.Fprintf(&bldr, "  %s: %s \n", ruleResp.Name, ruleResp.Status)
-		fmt.Fprintf(&bldr, "    %s \n", ruleResp.Message)
+		fmt.Fprintf(&bldr, "  %s: %s \n", ruleResp.Name(), ruleResp.Status())
+		fmt.Fprintf(&bldr, "    %s \n", ruleResp.Message())
 	}
 
 	return bldr.String()
diff --git a/cmd/cli/kubectl-kyverno/utils/common/common.go b/cmd/cli/kubectl-kyverno/utils/common/common.go
index dadb1dcfa3..8f90f21f2a 100644
--- a/cmd/cli/kubectl-kyverno/utils/common/common.go
+++ b/cmd/cli/kubectl-kyverno/utils/common/common.go
@@ -708,15 +708,15 @@ func ProcessValidateEngineResponse(policy kyvernov1.PolicyInterface, validateRes
 		}
 
 		for i, valResponseRule := range validateResponse.PolicyResponse.Rules {
-			if policyRule.Name == valResponseRule.Name {
+			if policyRule.Name == valResponseRule.Name() {
 				ruleFoundInEngineResponse = true
 				vrule := kyvernov1.ViolatedRule{
-					Name:    valResponseRule.Name,
-					Type:    string(valResponseRule.Type),
-					Message: valResponseRule.Message,
+					Name:    valResponseRule.Name(),
+					Type:    string(valResponseRule.RuleType()),
+					Message: valResponseRule.Message(),
 				}
 
-				switch valResponseRule.Status {
+				switch valResponseRule.Status() {
 				case engineapi.RuleStatusPass:
 					rc.Pass++
 					vrule.Status = policyreportv1alpha2.StatusPass
@@ -747,7 +747,7 @@ func ProcessValidateEngineResponse(policy kyvernov1.PolicyInterface, validateRes
 							printCount++
 						}
 
-						fmt.Printf("%d. %s: %s \n", i+1, valResponseRule.Name, valResponseRule.Message)
+						fmt.Printf("%d. %s: %s \n", i+1, valResponseRule.Name(), valResponseRule.Message())
 					}
 
 				case engineapi.RuleStatusError:
@@ -801,17 +801,17 @@ func updateResultCounts(policy kyvernov1.PolicyInterface, engineResponse *engine
 	for _, policyRule := range autogen.ComputeRules(policy) {
 		ruleFoundInEngineResponse := false
 		for i, ruleResponse := range engineResponse.PolicyResponse.Rules {
-			if policyRule.Name == ruleResponse.Name {
+			if policyRule.Name == ruleResponse.Name() {
 				ruleFoundInEngineResponse = true
 
-				if ruleResponse.Status == engineapi.RuleStatusPass {
+				if ruleResponse.Status() == engineapi.RuleStatusPass {
 					rc.Pass++
 				} else {
 					if printCount < 1 {
 						fmt.Println("\ninvalid resource", "policy", policy.GetName(), "resource", resPath)
 						printCount++
 					}
-					fmt.Printf("%d. %s - %s\n", i+1, ruleResponse.Name, ruleResponse.Message)
+					fmt.Printf("%d. %s - %s\n", i+1, ruleResponse.Name(), ruleResponse.Message())
 
 					if auditWarn && engineResponse.GetValidationFailureAction().Audit() {
 						rc.Warn++
@@ -877,23 +877,23 @@ func processMutateEngineResponse(c ApplyPolicyConfig, mutateResponse *engineapi.
 	for _, policyRule := range autogen.ComputeRules(c.Policy) {
 		ruleFoundInEngineResponse := false
 		for i, mutateResponseRule := range mutateResponse.PolicyResponse.Rules {
-			if policyRule.Name == mutateResponseRule.Name {
+			if policyRule.Name == mutateResponseRule.Name() {
 				ruleFoundInEngineResponse = true
-				if mutateResponseRule.Status == engineapi.RuleStatusPass {
+				if mutateResponseRule.Status() == engineapi.RuleStatusPass {
 					c.Rc.Pass++
 					printMutatedRes = true
-				} else if mutateResponseRule.Status == engineapi.RuleStatusSkip {
+				} else if mutateResponseRule.Status() == engineapi.RuleStatusSkip {
 					fmt.Printf("\nskipped mutate policy %s -> resource %s", c.Policy.GetName(), resPath)
 					c.Rc.Skip++
-				} else if mutateResponseRule.Status == engineapi.RuleStatusError {
-					fmt.Printf("\nerror while applying mutate policy %s -> resource %s\nerror: %s", c.Policy.GetName(), resPath, mutateResponseRule.Message)
+				} else if mutateResponseRule.Status() == engineapi.RuleStatusError {
+					fmt.Printf("\nerror while applying mutate policy %s -> resource %s\nerror: %s", c.Policy.GetName(), resPath, mutateResponseRule.Message())
 					c.Rc.Error++
 				} else {
 					if printCount < 1 {
 						fmt.Printf("\nfailed to apply mutate policy %s -> resource %s", c.Policy.GetName(), resPath)
 						printCount++
 					}
-					fmt.Printf("%d. %s - %s \n", i+1, mutateResponseRule.Name, mutateResponseRule.Message)
+					fmt.Printf("%d. %s - %s \n", i+1, mutateResponseRule.Name(), mutateResponseRule.Message())
 					c.Rc.Fail++
 				}
 				continue
@@ -1089,7 +1089,7 @@ func handleGeneratePolicy(generateResponse *engineapi.EngineResponse, policyCont
 	objects := []runtime.Object{&resource}
 	resources := []*unstructured.Unstructured{}
 	for _, rule := range generateResponse.PolicyResponse.Rules {
-		if path, ok := ruleToCloneSourceResource[rule.Name]; ok {
+		if path, ok := ruleToCloneSourceResource[rule.Name()]; ok {
 			resourceBytes, err := getFileBytes(path)
 			if err != nil {
 				fmt.Printf("failed to get resource bytes\n")
@@ -1128,20 +1128,17 @@ func handleGeneratePolicy(generateResponse *engineapi.EngineResponse, policyCont
 	var newRuleResponse []engineapi.RuleResponse
 
 	for _, rule := range generateResponse.PolicyResponse.Rules {
-		genResource, err := c.ApplyGeneratePolicy(log.Log, &policyContext, gr, []string{rule.Name})
+		genResource, err := c.ApplyGeneratePolicy(log.Log, &policyContext, gr, []string{rule.Name()})
 		if err != nil {
-			rule.Status = engineapi.RuleStatusError
 			return nil, err
 		}
 
 		unstrGenResource, err := c.GetUnstrResource(genResource[0])
 		if err != nil {
-			rule.Status = engineapi.RuleStatusError
 			return nil, err
 		}
 
-		rule.GeneratedResource = *unstrGenResource
-		newRuleResponse = append(newRuleResponse, rule)
+		newRuleResponse = append(newRuleResponse, *rule.WithGeneratedResource(*unstrGenResource))
 	}
 
 	return newRuleResponse, nil
diff --git a/pkg/background/generate/generate.go b/pkg/background/generate/generate.go
index d8d2624e59..b2060c1875 100644
--- a/pkg/background/generate/generate.go
+++ b/pkg/background/generate/generate.go
@@ -223,7 +223,7 @@ func (c *GenerateController) applyGenerate(resource unstructured.Unstructured, u
 	var applicableRules []string
 	// Removing UR if rule is failed. Used when the generate condition failed but ur exist
 	for _, r := range engineResponse.PolicyResponse.Rules {
-		if r.Status != engineapi.RuleStatusPass {
+		if r.Status() != engineapi.RuleStatusPass {
 			logger.V(4).Info("querying all update requests")
 			selector := labels.SelectorFromSet(labels.Set(map[string]string{
 				kyvernov1beta1.URGeneratePolicyLabel:       engineResponse.Policy.GetName(),
@@ -244,7 +244,7 @@ func (c *GenerateController) applyGenerate(resource unstructured.Unstructured, u
 				}
 			}
 		} else {
-			applicableRules = append(applicableRules, r.Name)
+			applicableRules = append(applicableRules, r.Name())
 		}
 	}
 
diff --git a/pkg/background/mutate/mutate.go b/pkg/background/mutate/mutate.go
index dac4261856..0280d846f3 100644
--- a/pkg/background/mutate/mutate.go
+++ b/pkg/background/mutate/mutate.go
@@ -149,17 +149,16 @@ func (c *MutateExistingController) ProcessUR(ur *kyvernov1beta1.UpdateRequest) e
 
 		er := c.engine.Mutate(context.TODO(), policyContext)
 		for _, r := range er.PolicyResponse.Rules {
-			patched := r.PatchedTarget
-			patchedTargetSubresourceName := r.PatchedTargetSubresourceName
-			switch r.Status {
+			patched, parentGVR, patchedSubresource := r.PatchedTarget()
+			switch r.Status() {
 			case engineapi.RuleStatusFail, engineapi.RuleStatusError, engineapi.RuleStatusWarn:
-				err := fmt.Errorf("failed to mutate existing resource, rule response%v: %s", r.Status, r.Message)
+				err := fmt.Errorf("failed to mutate existing resource, rule response%v: %s", r.Status(), r.Message())
 				logger.Error(err, "")
 				errs = append(errs, err)
 				c.report(err, ur.Spec.Policy, rule.Name, patched)
 
 			case engineapi.RuleStatusSkip:
-				logger.Info("mutate existing rule skipped", "rule", r.Name, "message", r.Message)
+				logger.Info("mutate existing rule skipped", "rule", r.Name(), "message", r.Message())
 				c.report(err, ur.Spec.Policy, rule.Name, patched)
 
 			case engineapi.RuleStatusPass:
@@ -171,18 +170,18 @@ func (c *MutateExistingController) ProcessUR(ur *kyvernov1beta1.UpdateRequest) e
 				}
 
 				if patchedNew == nil {
-					logger.Error(ErrEmptyPatch, "", "rule", r.Name, "message", r.Message)
+					logger.Error(ErrEmptyPatch, "", "rule", r.Name(), "message", r.Message())
 					errs = append(errs, err)
 					continue
 				}
 
-				if r.Status == engineapi.RuleStatusPass {
+				if r.Status() == engineapi.RuleStatusPass {
 					patchedNew.SetResourceVersion(patched.GetResourceVersion())
 					var updateErr error
-					if patchedTargetSubresourceName == "status" {
+					if patchedSubresource == "status" {
 						_, updateErr = c.client.UpdateStatusResource(context.TODO(), patchedNew.GetAPIVersion(), patchedNew.GetKind(), patchedNew.GetNamespace(), patchedNew.Object, false)
-					} else if patchedTargetSubresourceName != "" {
-						parentResourceGVR := r.PatchedTargetParentResourceGVR
+					} else if patchedSubresource != "" {
+						parentResourceGVR := parentGVR
 						parentResourceGV := schema.GroupVersion{Group: parentResourceGVR.Group, Version: parentResourceGVR.Version}
 						parentResourceGVK, err := c.client.Discovery().GetGVKFromGVR(parentResourceGV.WithResource(parentResourceGVR.Resource))
 						if err != nil {
@@ -190,7 +189,7 @@ func (c *MutateExistingController) ProcessUR(ur *kyvernov1beta1.UpdateRequest) e
 							errs = append(errs, err)
 							continue
 						}
-						_, updateErr = c.client.UpdateResource(context.TODO(), parentResourceGV.String(), parentResourceGVK.Kind, patchedNew.GetNamespace(), patchedNew.Object, false, patchedTargetSubresourceName)
+						_, updateErr = c.client.UpdateResource(context.TODO(), parentResourceGV.String(), parentResourceGVK.Kind, patchedNew.GetNamespace(), patchedNew.Object, false, patchedSubresource)
 					} else {
 						_, updateErr = c.client.UpdateResource(context.TODO(), patchedNew.GetAPIVersion(), patchedNew.GetKind(), patchedNew.GetNamespace(), patchedNew.Object, false)
 					}
@@ -261,7 +260,7 @@ func addAnnotation(policy kyvernov1.PolicyInterface, patched *unstructured.Unstr
 	patchedNew = patched
 	var rulePatches []utils.RulePatch
 
-	for _, patch := range r.Patches {
+	for _, patch := range r.Patches() {
 		var patchmap map[string]interface{}
 		if err := json.Unmarshal(patch, &patchmap); err != nil {
 			return nil, fmt.Errorf("failed to parse JSON patch bytes: %v", err)
@@ -272,7 +271,7 @@ func addAnnotation(policy kyvernov1.PolicyInterface, patched *unstructured.Unstr
 			Op       string `json:"op"`
 			Path     string `json:"path"`
 		}{
-			RuleName: r.Name,
+			RuleName: r.Name(),
 			Op:       patchmap["op"].(string),
 			Path:     patchmap["path"].(string),
 		}
diff --git a/pkg/controllers/report/utils/events.go b/pkg/controllers/report/utils/events.go
index 222af092c9..52952c0897 100644
--- a/pkg/controllers/report/utils/events.go
+++ b/pkg/controllers/report/utils/events.go
@@ -34,8 +34,7 @@ func generateSuccessEvents(log logr.Logger, ers ...engineapi.EngineResponse) (ev
 func generateExceptionEvents(log logr.Logger, ers ...engineapi.EngineResponse) (eventInfos []event.Info) {
 	for _, er := range ers {
 		for i, ruleResp := range er.PolicyResponse.Rules {
-			isException := ruleResp.Exception != nil
-			if ruleResp.Status == engineapi.RuleStatusSkip && isException {
+			if ruleResp.Status() == engineapi.RuleStatusSkip && ruleResp.IsException() {
 				eventInfos = append(eventInfos, event.NewPolicyExceptionEvents(er, &er.PolicyResponse.Rules[i], event.PolicyController)...)
 			}
 		}
@@ -59,7 +58,7 @@ func generateFailEventsPerEr(log logr.Logger, er engineapi.EngineResponse) []eve
 		"name", er.Resource.GetName(),
 	)
 	for i, rule := range er.PolicyResponse.Rules {
-		if rule.Status != engineapi.RuleStatusPass && rule.Status != engineapi.RuleStatusSkip {
+		if rule.Status() != engineapi.RuleStatusPass && rule.Status() != engineapi.RuleStatusSkip {
 			eventResource := event.NewResourceViolationEvent(event.PolicyController, event.PolicyViolation, er, &er.PolicyResponse.Rules[i])
 			eventInfos = append(eventInfos, eventResource)
 			eventPolicy := event.NewPolicyFailEvent(event.PolicyController, event.PolicyViolation, er, &er.PolicyResponse.Rules[i], false)
diff --git a/pkg/engine/api/engineresponse.go b/pkg/engine/api/engineresponse.go
index 1b68d87006..18444a1dca 100644
--- a/pkg/engine/api/engineresponse.go
+++ b/pkg/engine/api/engineresponse.go
@@ -124,9 +124,7 @@ func (er EngineResponse) IsNil() bool {
 func (er EngineResponse) GetPatches() [][]byte {
 	var patches [][]byte
 	for _, r := range er.PolicyResponse.Rules {
-		if r.Patches != nil {
-			patches = append(patches, r.Patches...)
-		}
+		patches = append(patches, r.Patches()...)
 	}
 	return patches
 }
@@ -161,7 +159,7 @@ func (er EngineResponse) getRules(predicate func(RuleResponse) bool) []string {
 	var rules []string
 	for _, r := range er.PolicyResponse.Rules {
 		if predicate(r) {
-			rules = append(rules, r.Name)
+			rules = append(rules, r.Name())
 		}
 	}
 	return rules
@@ -171,7 +169,7 @@ func (er EngineResponse) getRulesWithErrors(predicate func(RuleResponse) bool) [
 	var rules []string
 	for _, r := range er.PolicyResponse.Rules {
 		if predicate(r) {
-			rules = append(rules, fmt.Sprintf("%s: %s", r.Name, r.Message))
+			rules = append(rules, fmt.Sprintf("%s: %s", r.Name(), r.Message()))
 		}
 	}
 	return rules
diff --git a/pkg/engine/api/engineresponse_test.go b/pkg/engine/api/engineresponse_test.go
index aa90934e80..668b5e1dc4 100644
--- a/pkg/engine/api/engineresponse_test.go
+++ b/pkg/engine/api/engineresponse_test.go
@@ -113,18 +113,18 @@ func TestEngineResponse_IsOneOf(t *testing.T) {
 	}{{
 		fields: fields{
 			PolicyResponse: PolicyResponse{
-				Rules: []RuleResponse{{
-					Status: RuleStatusFail,
-				}},
+				Rules: []RuleResponse{
+					*RuleFail("", Validation, ""),
+				},
 			},
 		},
 		want: false,
 	}, {
 		fields: fields{
 			PolicyResponse: PolicyResponse{
-				Rules: []RuleResponse{{
-					Status: RuleStatusFail,
-				}},
+				Rules: []RuleResponse{
+					*RuleFail("", Validation, ""),
+				},
 			},
 		},
 		args: args{
@@ -134,9 +134,9 @@ func TestEngineResponse_IsOneOf(t *testing.T) {
 	}, {
 		fields: fields{
 			PolicyResponse: PolicyResponse{
-				Rules: []RuleResponse{{
-					Status: RuleStatusFail,
-				}},
+				Rules: []RuleResponse{
+					*RuleFail("", Validation, ""),
+				},
 			},
 		},
 		args: args{
@@ -146,9 +146,9 @@ func TestEngineResponse_IsOneOf(t *testing.T) {
 	}, {
 		fields: fields{
 			PolicyResponse: PolicyResponse{
-				Rules: []RuleResponse{{
-					Status: RuleStatusFail,
-				}},
+				Rules: []RuleResponse{
+					*RuleFail("", Validation, ""),
+				},
 			},
 		},
 		args: args{
@@ -158,9 +158,9 @@ func TestEngineResponse_IsOneOf(t *testing.T) {
 	}, {
 		fields: fields{
 			PolicyResponse: PolicyResponse{
-				Rules: []RuleResponse{{
-					Status: RuleStatusFail,
-				}},
+				Rules: []RuleResponse{
+					*RuleFail("", Validation, ""),
+				},
 			},
 		},
 		args: args{
@@ -199,45 +199,45 @@ func TestEngineResponse_IsSuccessful(t *testing.T) {
 	}, {
 		fields: fields{
 			PolicyResponse: PolicyResponse{
-				Rules: []RuleResponse{{
-					Status: RuleStatusPass,
-				}},
+				Rules: []RuleResponse{
+					*RulePass("", Validation, ""),
+				},
 			},
 		},
 		want: true,
 	}, {
 		fields: fields{
 			PolicyResponse: PolicyResponse{
-				Rules: []RuleResponse{{
-					Status: RuleStatusFail,
-				}},
+				Rules: []RuleResponse{
+					*RuleFail("", Validation, ""),
+				},
 			},
 		},
 		want: false,
 	}, {
 		fields: fields{
 			PolicyResponse: PolicyResponse{
-				Rules: []RuleResponse{{
-					Status: RuleStatusWarn,
-				}},
+				Rules: []RuleResponse{
+					*RuleWarn("", Validation, ""),
+				},
 			},
 		},
 		want: true,
 	}, {
 		fields: fields{
 			PolicyResponse: PolicyResponse{
-				Rules: []RuleResponse{{
-					Status: RuleStatusError,
-				}},
+				Rules: []RuleResponse{
+					*RuleError("", Validation, "", nil),
+				},
 			},
 		},
 		want: false,
 	}, {
 		fields: fields{
 			PolicyResponse: PolicyResponse{
-				Rules: []RuleResponse{{
-					Status: RuleStatusSkip,
-				}},
+				Rules: []RuleResponse{
+					*RuleSkip("", Validation, ""),
+				},
 			},
 		},
 		want: true,
@@ -273,45 +273,45 @@ func TestEngineResponse_IsSkipped(t *testing.T) {
 	}, {
 		fields: fields{
 			PolicyResponse: PolicyResponse{
-				Rules: []RuleResponse{{
-					Status: RuleStatusPass,
-				}},
+				Rules: []RuleResponse{
+					*RulePass("", Validation, ""),
+				},
 			},
 		},
 		want: false,
 	}, {
 		fields: fields{
 			PolicyResponse: PolicyResponse{
-				Rules: []RuleResponse{{
-					Status: RuleStatusFail,
-				}},
+				Rules: []RuleResponse{
+					*RuleFail("", Validation, ""),
+				},
 			},
 		},
 		want: false,
 	}, {
 		fields: fields{
 			PolicyResponse: PolicyResponse{
-				Rules: []RuleResponse{{
-					Status: RuleStatusWarn,
-				}},
+				Rules: []RuleResponse{
+					*RuleWarn("", Validation, ""),
+				},
 			},
 		},
 		want: false,
 	}, {
 		fields: fields{
 			PolicyResponse: PolicyResponse{
-				Rules: []RuleResponse{{
-					Status: RuleStatusError,
-				}},
+				Rules: []RuleResponse{
+					*RuleError("", Validation, "", nil),
+				},
 			},
 		},
 		want: false,
 	}, {
 		fields: fields{
 			PolicyResponse: PolicyResponse{
-				Rules: []RuleResponse{{
-					Status: RuleStatusSkip,
-				}},
+				Rules: []RuleResponse{
+					*RuleSkip("", Validation, ""),
+				},
 			},
 		},
 		want: true,
@@ -347,45 +347,45 @@ func TestEngineResponse_IsFailed(t *testing.T) {
 	}, {
 		fields: fields{
 			PolicyResponse: PolicyResponse{
-				Rules: []RuleResponse{{
-					Status: RuleStatusPass,
-				}},
+				Rules: []RuleResponse{
+					*RulePass("", Validation, ""),
+				},
 			},
 		},
 		want: false,
 	}, {
 		fields: fields{
 			PolicyResponse: PolicyResponse{
-				Rules: []RuleResponse{{
-					Status: RuleStatusFail,
-				}},
+				Rules: []RuleResponse{
+					*RuleFail("", Validation, ""),
+				},
 			},
 		},
 		want: true,
 	}, {
 		fields: fields{
 			PolicyResponse: PolicyResponse{
-				Rules: []RuleResponse{{
-					Status: RuleStatusWarn,
-				}},
+				Rules: []RuleResponse{
+					*RuleWarn("", Validation, ""),
+				},
 			},
 		},
 		want: false,
 	}, {
 		fields: fields{
 			PolicyResponse: PolicyResponse{
-				Rules: []RuleResponse{{
-					Status: RuleStatusError,
-				}},
+				Rules: []RuleResponse{
+					*RuleError("", Validation, "", nil),
+				},
 			},
 		},
 		want: false,
 	}, {
 		fields: fields{
 			PolicyResponse: PolicyResponse{
-				Rules: []RuleResponse{{
-					Status: RuleStatusSkip,
-				}},
+				Rules: []RuleResponse{
+					*RuleSkip("", Validation, ""),
+				},
 			},
 		},
 		want: false,
@@ -421,45 +421,45 @@ func TestEngineResponse_IsError(t *testing.T) {
 	}, {
 		fields: fields{
 			PolicyResponse: PolicyResponse{
-				Rules: []RuleResponse{{
-					Status: RuleStatusPass,
-				}},
+				Rules: []RuleResponse{
+					*RulePass("", Validation, ""),
+				},
 			},
 		},
 		want: false,
 	}, {
 		fields: fields{
 			PolicyResponse: PolicyResponse{
-				Rules: []RuleResponse{{
-					Status: RuleStatusFail,
-				}},
+				Rules: []RuleResponse{
+					*RuleFail("", Validation, ""),
+				},
 			},
 		},
 		want: false,
 	}, {
 		fields: fields{
 			PolicyResponse: PolicyResponse{
-				Rules: []RuleResponse{{
-					Status: RuleStatusWarn,
-				}},
+				Rules: []RuleResponse{
+					*RuleWarn("", Validation, ""),
+				},
 			},
 		},
 		want: false,
 	}, {
 		fields: fields{
 			PolicyResponse: PolicyResponse{
-				Rules: []RuleResponse{{
-					Status: RuleStatusError,
-				}},
+				Rules: []RuleResponse{
+					*RuleError("", Validation, "", nil),
+				},
 			},
 		},
 		want: true,
 	}, {
 		fields: fields{
 			PolicyResponse: PolicyResponse{
-				Rules: []RuleResponse{{
-					Status: RuleStatusSkip,
-				}},
+				Rules: []RuleResponse{
+					*RuleSkip("", Validation, ""),
+				},
 			},
 		},
 		want: false,
@@ -493,76 +493,63 @@ func TestEngineResponse_GetFailedRules(t *testing.T) {
 	}{{
 		fields: fields{
 			PolicyResponse: PolicyResponse{
-				Rules: []RuleResponse{{
-					Name:   "skip",
-					Status: RuleStatusSkip,
-				}},
+				Rules: []RuleResponse{
+					*RuleSkip("skip", Validation, ""),
+				},
 			},
 		},
 	}, {
 		fields: fields{
 			PolicyResponse: PolicyResponse{
-				Rules: []RuleResponse{{
-					Name:   "warn",
-					Status: RuleStatusWarn,
-				}},
+				Rules: []RuleResponse{
+					*RuleWarn("warn", Validation, ""),
+				},
 			},
 		},
 	}, {
 		fields: fields{
 			PolicyResponse: PolicyResponse{
-				Rules: []RuleResponse{{
-					Name:   "pass",
-					Status: RuleStatusPass,
-				}},
+				Rules: []RuleResponse{
+					*RulePass("pass", Validation, ""),
+				},
 			},
 		},
 	}, {
 		fields: fields{
 			PolicyResponse: PolicyResponse{
-				Rules: []RuleResponse{{
-					Name:   "fail",
-					Status: RuleStatusFail,
-				}},
+				Rules: []RuleResponse{
+					*RuleFail("fail", Validation, ""),
+				},
 			},
 		},
 		want: []string{"fail"},
 	}, {
 		fields: fields{
 			PolicyResponse: PolicyResponse{
-				Rules: []RuleResponse{{
-					Name:   "fail-1",
-					Status: RuleStatusFail,
-				}, {
-					Name:   "fail-2",
-					Status: RuleStatusFail,
-				}},
+				Rules: []RuleResponse{
+					*RuleFail("fail-1", Validation, ""),
+					*RuleFail("fail-2", Validation, ""),
+				},
 			},
 		},
 		want: []string{"fail-1", "fail-2"},
 	}, {
 		fields: fields{
 			PolicyResponse: PolicyResponse{
-				Rules: []RuleResponse{{
-					Name:   "fail-1",
-					Status: RuleStatusFail,
-				}, {
-					Name:   "error-1",
-					Status: RuleStatusError,
-				}},
+				Rules: []RuleResponse{
+					*RuleFail("fail-1", Validation, ""),
+					*RuleError("error-1", Validation, "", nil),
+				},
 			},
 		},
 		want: []string{"fail-1", "error-1"},
 	}, {
 		fields: fields{
 			PolicyResponse: PolicyResponse{
-				Rules: []RuleResponse{{
-					Name:   "error-1",
-					Status: RuleStatusError,
-				}, {
-					Name:   "error-2",
-					Status: RuleStatusError,
-				}},
+				Rules: []RuleResponse{
+					*RuleError("error-1", Validation, "", nil),
+					*RuleError("error-2", Validation, "", nil),
+				},
 			},
 		},
 		want: []string{"error-1", "error-2"},
@@ -596,113 +583,91 @@ func TestEngineResponse_GetSuccessRules(t *testing.T) {
 	}{{
 		fields: fields{
 			PolicyResponse: PolicyResponse{
-				Rules: []RuleResponse{{
-					Name:   "skip",
-					Status: RuleStatusSkip,
-				}},
+				Rules: []RuleResponse{
+					*RuleSkip("skip", Validation, ""),
+				},
 			},
 		},
 	}, {
 		fields: fields{
 			PolicyResponse: PolicyResponse{
-				Rules: []RuleResponse{{
-					Name:   "warn",
-					Status: RuleStatusWarn,
-				}},
+				Rules: []RuleResponse{
+					*RuleWarn("warn", Validation, ""),
+				},
 			},
 		},
 	}, {
 		fields: fields{
 			PolicyResponse: PolicyResponse{
-				Rules: []RuleResponse{{
-					Name:   "pass-1",
-					Status: RuleStatusPass,
-				}, {
-					Name:   "pass-2",
-					Status: RuleStatusPass,
-				}},
+				Rules: []RuleResponse{
+					*RulePass("pass-1", Validation, ""),
+					*RulePass("pass-2", Validation, ""),
+				},
 			},
 		},
 		want: []string{"pass-1", "pass-2"},
 	}, {
 		fields: fields{
 			PolicyResponse: PolicyResponse{
-				Rules: []RuleResponse{{
-					Name:   "pass",
-					Status: RuleStatusPass,
-				}},
+				Rules: []RuleResponse{
+					*RulePass("pass", Validation, ""),
+				},
 			},
 		},
 		want: []string{"pass"},
 	}, {
 		fields: fields{
 			PolicyResponse: PolicyResponse{
-				Rules: []RuleResponse{{
-					Name:   "pass",
-					Status: RuleStatusPass,
-				}, {
-					Name:   "fail",
-					Status: RuleStatusFail,
-				}},
+				Rules: []RuleResponse{
+					*RulePass("pass", Validation, ""),
+					*RuleFail("fail", Validation, ""),
+				},
 			},
 		},
 		want: []string{"pass"},
 	}, {
 		fields: fields{
 			PolicyResponse: PolicyResponse{
-				Rules: []RuleResponse{{
-					Name:   "pass",
-					Status: RuleStatusPass,
-				}, {
-					Name:   "skip",
-					Status: RuleStatusSkip,
-				}},
+				Rules: []RuleResponse{
+					*RulePass("pass", Validation, ""),
+					*RuleSkip("skip", Validation, ""),
+				},
 			},
 		},
 		want: []string{"pass"},
 	}, {
 		fields: fields{
 			PolicyResponse: PolicyResponse{
-				Rules: []RuleResponse{{
-					Name:   "fail",
-					Status: RuleStatusFail,
-				}},
+				Rules: []RuleResponse{
+					*RuleFail("fail", Validation, ""),
+				},
 			},
 		},
 	}, {
 		fields: fields{
 			PolicyResponse: PolicyResponse{
-				Rules: []RuleResponse{{
-					Name:   "fail-1",
-					Status: RuleStatusFail,
-				}, {
-					Name:   "fail-2",
-					Status: RuleStatusFail,
-				}},
+				Rules: []RuleResponse{
+					*RuleFail("fail-1", Validation, ""),
+					*RuleFail("fail-2", Validation, ""),
+				},
 			},
 		},
 	}, {
 		fields: fields{
 			PolicyResponse: PolicyResponse{
-				Rules: []RuleResponse{{
-					Name:   "fail-1",
-					Status: RuleStatusFail,
-				}, {
-					Name:   "error-1",
-					Status: RuleStatusError,
-				}},
+				Rules: []RuleResponse{
+					*RuleFail("fail-1", Validation, ""),
+					*RuleError("error-1", Validation, "", nil),
+				},
 			},
 		},
 	}, {
 		fields: fields{
 			PolicyResponse: PolicyResponse{
-				Rules: []RuleResponse{{
-					Name:   "error-1",
-					Status: RuleStatusError,
-				}, {
-					Name:   "error-2",
-					Status: RuleStatusError,
-				}},
+				Rules: []RuleResponse{
+					*RuleError("error-1", Validation, "", nil),
+					*RuleError("error-2", Validation, "", nil),
+				},
 			},
 		},
 	}}
@@ -984,20 +949,21 @@ func TestEngineResponse_GetPatches(t *testing.T) {
 	}, {
 		fields: fields{
 			PolicyResponse: PolicyResponse{
-				Rules: []RuleResponse{{}, {
-					Patches: [][]byte{{0, 1, 2}, {3, 4, 5}},
-				}},
+				Rules: []RuleResponse{
+					{},
+					*RuleResponse{}.WithPatches([][]byte{{0, 1, 2}, {3, 4, 5}}...),
+				},
 			},
 		},
 		want: [][]byte{{0, 1, 2}, {3, 4, 5}},
 	}, {
 		fields: fields{
 			PolicyResponse: PolicyResponse{
-				Rules: []RuleResponse{{}, {
-					Patches: [][]byte{{0, 1, 2}, {3, 4, 5}},
-				}, {
-					Patches: [][]byte{{7, 8, 9}},
-				}},
+				Rules: []RuleResponse{
+					{},
+					*RuleResponse{}.WithPatches([][]byte{{0, 1, 2}, {3, 4, 5}}...),
+					*RuleResponse{}.WithPatches([][]byte{{7, 8, 9}}...),
+				},
 			},
 		},
 		want: [][]byte{{0, 1, 2}, {3, 4, 5}, {7, 8, 9}},
diff --git a/pkg/engine/api/policyresponse.go b/pkg/engine/api/policyresponse.go
index e31ee327a2..772ef5e7b0 100644
--- a/pkg/engine/api/policyresponse.go
+++ b/pkg/engine/api/policyresponse.go
@@ -1,5 +1,7 @@
 package api
 
+import "time"
+
 // PolicyResponse policy application response
 type PolicyResponse struct {
 	// Stats contains policy statistics
@@ -8,11 +10,12 @@ type PolicyResponse struct {
 	Rules []RuleResponse
 }
 
-func (pr *PolicyResponse) Add(rr RuleResponse) {
-	pr.Rules = append(pr.Rules, rr)
-	if rr.Status == RuleStatusPass || rr.Status == RuleStatusFail {
+func (pr *PolicyResponse) Add(startTime, endTime time.Time, response RuleResponse) {
+	pr.Rules = append(pr.Rules, response.WithStats(startTime, endTime))
+	status := response.Status()
+	if status == RuleStatusPass || status == RuleStatusFail {
 		pr.Stats.RulesAppliedCount++
-	} else if rr.Status == RuleStatusError {
+	} else if status == RuleStatusError {
 		pr.Stats.RulesErrorCount++
 	}
 }
diff --git a/pkg/engine/api/ruleresponse.go b/pkg/engine/api/ruleresponse.go
index bf2c7b471c..1b16efc673 100644
--- a/pkg/engine/api/ruleresponse.go
+++ b/pkg/engine/api/ruleresponse.go
@@ -2,6 +2,7 @@ package api
 
 import (
 	"fmt"
+	"time"
 
 	kyvernov2alpha1 "github.com/kyverno/kyverno/api/kyverno/v2alpha1"
 	pssutils "github.com/kyverno/kyverno/pkg/pss/utils"
@@ -22,36 +23,145 @@ type PodSecurityChecks struct {
 
 // RuleResponse details for each rule application
 type RuleResponse struct {
-	// Name is the rule name specified in policy
-	Name string
-	// Type is the rule type (Mutation,Generation,Validation) for Kyverno Policy
-	Type RuleType
-	// Message is the message response from the rule application
-	Message string
-	// Patches are JSON patches, for mutation rules
-	Patches [][]byte
-	// GeneratedResource is the generated by the generate rules of a policy
-	GeneratedResource unstructured.Unstructured
-	// Status rule status
-	Status RuleStatus
-	// Stats contains rule statistics
-	Stats ExecutionStats
-	// PatchedTarget is the patched resource for mutate.targets
-	PatchedTarget *unstructured.Unstructured
-	// PatchedTargetSubresourceName is the name of the subresource which is patched, empty if the resource patched is not a subresource.
-	PatchedTargetSubresourceName string
-	// PatchedTargetParentResourceGVR is the GVR of the parent resource of the PatchedTarget. This is only populated when PatchedTarget is a subresource.
-	PatchedTargetParentResourceGVR metav1.GroupVersionResource
-	// PodSecurityChecks contains pod security checks (only if this is a pod security rule)
-	PodSecurityChecks *PodSecurityChecks
-	// Exception is the exception applied (if any)
-	Exception *kyvernov2alpha1.PolicyException
+	// name is the rule name specified in policy
+	name string
+	// ruleType is the rule type (Mutation,Generation,Validation) for Kyverno Policy
+	ruleType RuleType
+	// message is the message response from the rule application
+	message string
+	// status rule status
+	status RuleStatus
+	// patches are JSON patches, for mutation rules
+	patches [][]byte
+	// stats contains rule statistics
+	stats ExecutionStats
+	// generatedResource is the generated by the generate rules of a policy
+	generatedResource unstructured.Unstructured
+	// patchedTarget is the patched resource for mutate.targets
+	patchedTarget *unstructured.Unstructured
+	// patchedTargetParentResourceGVR is the GVR of the parent resource of the PatchedTarget. This is only populated when PatchedTarget is a subresource.
+	patchedTargetParentResourceGVR metav1.GroupVersionResource
+	// patchedTargetSubresourceName is the name of the subresource which is patched, empty if the resource patched is not a subresource.
+	patchedTargetSubresourceName string
+	// podSecurityChecks contains pod security checks (only if this is a pod security rule)
+	podSecurityChecks *PodSecurityChecks
+	// exception is the exception applied (if any)
+	exception *kyvernov2alpha1.PolicyException
+}
+
+func NewRuleResponse(name string, ruleType RuleType, msg string, status RuleStatus) *RuleResponse {
+	return &RuleResponse{
+		name:     name,
+		ruleType: ruleType,
+		message:  msg,
+		status:   status,
+	}
+}
+
+func RuleError(name string, ruleType RuleType, msg string, err error) *RuleResponse {
+	if err != nil {
+		return NewRuleResponse(name, ruleType, fmt.Sprintf("%s: %s", msg, err.Error()), RuleStatusError)
+	}
+	return NewRuleResponse(name, ruleType, msg, RuleStatusError)
+}
+
+func RuleSkip(name string, ruleType RuleType, msg string) *RuleResponse {
+	return NewRuleResponse(name, ruleType, msg, RuleStatusSkip)
+}
+
+func RuleWarn(name string, ruleType RuleType, msg string) *RuleResponse {
+	return NewRuleResponse(name, ruleType, msg, RuleStatusWarn)
+}
+
+func RulePass(name string, ruleType RuleType, msg string) *RuleResponse {
+	return NewRuleResponse(name, ruleType, msg, RuleStatusPass)
+}
+
+func RuleFail(name string, ruleType RuleType, msg string) *RuleResponse {
+	return NewRuleResponse(name, ruleType, msg, RuleStatusFail)
+}
+
+func (r RuleResponse) WithException(exception *kyvernov2alpha1.PolicyException) *RuleResponse {
+	r.exception = exception
+	return &r
+}
+
+func (r RuleResponse) WithPodSecurityChecks(checks PodSecurityChecks) *RuleResponse {
+	r.podSecurityChecks = &checks
+	return &r
+}
+
+func (r RuleResponse) WithPatchedTarget(patchedTarget *unstructured.Unstructured, gvr metav1.GroupVersionResource, subresource string) *RuleResponse {
+	r.patchedTarget = patchedTarget
+	r.patchedTargetParentResourceGVR = gvr
+	r.patchedTargetSubresourceName = subresource
+	return &r
+}
+
+func (r RuleResponse) WithGeneratedResource(resource unstructured.Unstructured) *RuleResponse {
+	r.generatedResource = resource
+	return &r
+}
+
+func (r RuleResponse) WithPatches(patches ...[]byte) *RuleResponse {
+	r.patches = patches
+	return &r
+}
+
+func (r RuleResponse) WithStats(startTime, endTime time.Time) RuleResponse {
+	r.stats = NewExecutionStats(startTime)
+	r.stats.Done(endTime)
+	return r
+}
+
+func (r *RuleResponse) Stats() ExecutionStats {
+	return r.stats
+}
+
+func (r *RuleResponse) Exception() *kyvernov2alpha1.PolicyException {
+	return r.exception
+}
+
+func (r *RuleResponse) IsException() bool {
+	return r.exception != nil
+}
+
+func (r *RuleResponse) PodSecurityChecks() *PodSecurityChecks {
+	return r.podSecurityChecks
+}
+
+func (r *RuleResponse) PatchedTarget() (*unstructured.Unstructured, metav1.GroupVersionResource, string) {
+	return r.patchedTarget, r.patchedTargetParentResourceGVR, r.patchedTargetSubresourceName
+}
+
+func (r *RuleResponse) GeneratedResource() unstructured.Unstructured {
+	return r.generatedResource
+}
+
+func (r *RuleResponse) Patches() [][]byte {
+	return r.patches
+}
+
+func (r *RuleResponse) Message() string {
+	return r.message
+}
+
+func (r *RuleResponse) Name() string {
+	return r.name
+}
+
+func (r *RuleResponse) RuleType() RuleType {
+	return r.ruleType
+}
+
+func (r *RuleResponse) Status() RuleStatus {
+	return r.status
 }
 
 // HasStatus checks if rule status is in a given list
-func (r RuleResponse) HasStatus(status ...RuleStatus) bool {
+func (r *RuleResponse) HasStatus(status ...RuleStatus) bool {
 	for _, s := range status {
-		if r.Status == s {
+		if r.status == s {
 			return true
 		}
 	}
@@ -59,6 +169,6 @@ func (r RuleResponse) HasStatus(status ...RuleStatus) bool {
 }
 
 // String implements Stringer interface
-func (r RuleResponse) String() string {
-	return fmt.Sprintf("rule %s (%s): %v", r.Name, r.Type, r.Message)
+func (r *RuleResponse) String() string {
+	return fmt.Sprintf("rule %s (%s): %v", r.name, r.ruleType, r.message)
 }
diff --git a/pkg/engine/api/ruleresponse_test.go b/pkg/engine/api/ruleresponse_test.go
index 5dd6c2b8ed..c1bef933a8 100644
--- a/pkg/engine/api/ruleresponse_test.go
+++ b/pkg/engine/api/ruleresponse_test.go
@@ -2,24 +2,14 @@ package api
 
 import (
 	"testing"
-
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 )
 
 func TestRuleResponse_String(t *testing.T) {
 	type fields struct {
-		Name                           string
-		Type                           RuleType
-		Message                        string
-		Patches                        [][]byte
-		GeneratedResource              unstructured.Unstructured
-		Status                         RuleStatus
-		Stats                          ExecutionStats
-		PatchedTarget                  *unstructured.Unstructured
-		PatchedTargetSubresourceName   string
-		PatchedTargetParentResourceGVR metav1.GroupVersionResource
-		PodSecurityChecks              *PodSecurityChecks
+		Name    string
+		Type    RuleType
+		Message string
+		Status  RuleStatus
 	}
 	tests := []struct {
 		name   string
@@ -56,19 +46,12 @@ func TestRuleResponse_String(t *testing.T) {
 	}}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			rr := RuleResponse{
-				Name:                           tt.fields.Name,
-				Type:                           tt.fields.Type,
-				Message:                        tt.fields.Message,
-				Patches:                        tt.fields.Patches,
-				GeneratedResource:              tt.fields.GeneratedResource,
-				Status:                         tt.fields.Status,
-				Stats:                          tt.fields.Stats,
-				PatchedTarget:                  tt.fields.PatchedTarget,
-				PatchedTargetSubresourceName:   tt.fields.PatchedTargetSubresourceName,
-				PatchedTargetParentResourceGVR: tt.fields.PatchedTargetParentResourceGVR,
-				PodSecurityChecks:              tt.fields.PodSecurityChecks,
-			}
+			rr := NewRuleResponse(
+				tt.fields.Name,
+				tt.fields.Type,
+				tt.fields.Message,
+				tt.fields.Status,
+			)
 			if got := rr.String(); got != tt.want {
 				t.Errorf("RuleResponse.ToString() = %v, want %v", got, tt.want)
 			}
@@ -78,17 +61,10 @@ func TestRuleResponse_String(t *testing.T) {
 
 func TestRuleResponse_HasStatus(t *testing.T) {
 	type fields struct {
-		Name                           string
-		Type                           RuleType
-		Message                        string
-		Patches                        [][]byte
-		GeneratedResource              unstructured.Unstructured
-		Status                         RuleStatus
-		Stats                          ExecutionStats
-		PatchedTarget                  *unstructured.Unstructured
-		PatchedTargetSubresourceName   string
-		PatchedTargetParentResourceGVR metav1.GroupVersionResource
-		PodSecurityChecks              *PodSecurityChecks
+		Name    string
+		Type    RuleType
+		Message string
+		Status  RuleStatus
 	}
 	type args struct {
 		status []RuleStatus
@@ -138,19 +114,12 @@ func TestRuleResponse_HasStatus(t *testing.T) {
 	}}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			r := RuleResponse{
-				Name:                           tt.fields.Name,
-				Type:                           tt.fields.Type,
-				Message:                        tt.fields.Message,
-				Patches:                        tt.fields.Patches,
-				GeneratedResource:              tt.fields.GeneratedResource,
-				Status:                         tt.fields.Status,
-				Stats:                          tt.fields.Stats,
-				PatchedTarget:                  tt.fields.PatchedTarget,
-				PatchedTargetSubresourceName:   tt.fields.PatchedTargetSubresourceName,
-				PatchedTargetParentResourceGVR: tt.fields.PatchedTargetParentResourceGVR,
-				PodSecurityChecks:              tt.fields.PodSecurityChecks,
-			}
+			r := NewRuleResponse(
+				tt.fields.Name,
+				tt.fields.Type,
+				tt.fields.Message,
+				tt.fields.Status,
+			)
 			if got := r.HasStatus(tt.args.status...); got != tt.want {
 				t.Errorf("RuleResponse.HasStatus() = %v, want %v", got, tt.want)
 			}
diff --git a/pkg/engine/background.go b/pkg/engine/background.go
index a1b05ece66..37c04c833d 100644
--- a/pkg/engine/background.go
+++ b/pkg/engine/background.go
@@ -39,7 +39,7 @@ func (e *engine) filterRules(
 		logger := internal.LoggerWithRule(logger, rule)
 		if ruleResp := e.filterRule(rule, logger, policyContext); ruleResp != nil {
 			resp.Rules = append(resp.Rules, *ruleResp)
-			if applyRules == kyvernov1.ApplyOne && ruleResp.Status != engineapi.RuleStatusSkip {
+			if applyRules == kyvernov1.ApplyOne && ruleResp.Status() != engineapi.RuleStatusSkip {
 				break
 			}
 		}
@@ -67,8 +67,6 @@ func (e *engine) filterRule(
 		return ruleResp
 	}
 
-	startTime := time.Now()
-
 	newResource := policyContext.NewResource()
 	oldResource := policyContext.OldResource()
 	admissionInfo := policyContext.AdmissionInfo()
@@ -81,15 +79,7 @@ func (e *engine) filterRule(
 		if ruleType == engineapi.Generation {
 			// if the oldResource matched, return "false" to delete GR for it
 			if err = engineutils.MatchesResourceDescription(oldResource, rule, admissionInfo, namespaceLabels, policy.GetNamespace(), gvk, subresource, policyContext.Operation()); err == nil {
-				return &engineapi.RuleResponse{
-					Name:   rule.Name,
-					Type:   ruleType,
-					Status: engineapi.RuleStatusFail,
-					Stats: engineapi.ExecutionStats{
-						ProcessingTime: time.Since(startTime),
-						Timestamp:      startTime.Unix(),
-					},
-				}
+				return engineapi.RuleFail(rule.Name, ruleType, "")
 			}
 		}
 		logger.V(4).Info("rule not matched", "reason", err.Error())
@@ -122,17 +112,9 @@ func (e *engine) filterRule(
 	// evaluate pre-conditions
 	if !variables.EvaluateConditions(logger, ctx, copyConditions) {
 		logger.V(4).Info("skip rule as preconditions are not met", "rule", ruleCopy.Name)
-		return internal.RuleSkip(*ruleCopy, ruleType, "")
+		return engineapi.RuleSkip(ruleCopy.Name, ruleType, "")
 	}
 
 	// build rule Response
-	return &engineapi.RuleResponse{
-		Name:   ruleCopy.Name,
-		Type:   ruleType,
-		Status: engineapi.RuleStatusPass,
-		Stats: engineapi.ExecutionStats{
-			ProcessingTime: time.Since(startTime),
-			Timestamp:      startTime.Unix(),
-		},
-	}
+	return engineapi.RulePass(ruleCopy.Name, ruleType, "")
 }
diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go
index d954bb4118..d2162f462d 100644
--- a/pkg/engine/engine.go
+++ b/pkg/engine/engine.go
@@ -227,13 +227,13 @@ func (e *engine) invokeRuleHandler(
 				return resource, nil
 			}
 			if handlerFactory == nil {
-				return resource, handlers.RuleResponses(internal.RuleError(rule, ruleType, "failed to instantiate handler", nil))
+				return resource, handlers.WithError(rule, ruleType, "failed to instantiate handler", nil)
 			} else if handler, err := handlerFactory(); err != nil {
-				return resource, handlers.RuleResponses(internal.RuleError(rule, ruleType, "failed to instantiate handler", err))
+				return resource, handlers.WithError(rule, ruleType, "failed to instantiate handler", err)
 			} else if handler != nil {
 				// check if there's an exception
 				if ruleResp := e.hasPolicyExceptions(logger, ruleType, policyContext, rule); ruleResp != nil {
-					return resource, handlers.RuleResponses(ruleResp)
+					return resource, handlers.WithResponses(ruleResp)
 				}
 				// load rule context
 				contextLoader := e.ContextLoader(policyContext.Policy(), rule)
@@ -243,15 +243,15 @@ func (e *engine) invokeRuleHandler(
 					} else {
 						logger.Error(err, "failed to load context")
 					}
-					return resource, handlers.RuleResponses(internal.RuleError(rule, ruleType, "failed to load context", err))
+					return resource, handlers.WithError(rule, ruleType, "failed to load context", err)
 				}
 				// check preconditions
 				preconditionsPassed, err := internal.CheckPreconditions(logger, policyContext.JSONContext(), rule.GetAnyAllConditions())
 				if err != nil {
-					return resource, handlers.RuleResponses(internal.RuleError(rule, ruleType, "failed to evaluate preconditions", err))
+					return resource, handlers.WithError(rule, ruleType, "failed to evaluate preconditions", err)
 				}
 				if !preconditionsPassed {
-					return resource, handlers.RuleResponses(internal.RuleSkip(rule, ruleType, "preconditions not met"))
+					return resource, handlers.WithSkip(rule, ruleType, "preconditions not met")
 				}
 				// process handler
 				return handler.Process(ctx, logger, policyContext, resource, rule, contextLoader)
diff --git a/pkg/engine/exceptions.go b/pkg/engine/exceptions.go
index cd0aad8b6d..33d303b3c1 100644
--- a/pkg/engine/exceptions.go
+++ b/pkg/engine/exceptions.go
@@ -8,7 +8,6 @@ import (
 	kyvernov2alpha1 "github.com/kyverno/kyverno/api/kyverno/v2alpha1"
 	"github.com/kyverno/kyverno/pkg/config"
 	engineapi "github.com/kyverno/kyverno/pkg/engine/api"
-	"github.com/kyverno/kyverno/pkg/engine/internal"
 	matched "github.com/kyverno/kyverno/pkg/utils/match"
 	"k8s.io/apimachinery/pkg/labels"
 	"k8s.io/client-go/tools/cache"
@@ -79,18 +78,19 @@ func (e *engine) hasPolicyExceptions(
 ) *engineapi.RuleResponse {
 	// if matches, check if there is a corresponding policy exception
 	exception, err := matchesException(e.exceptionSelector, ctx, rule, e.configuration)
-	var response *engineapi.RuleResponse
-	// if we found an exception
-	if err == nil && exception != nil {
-		key, err := cache.MetaNamespaceKeyFunc(exception)
-		if err != nil {
-			logger.Error(err, "failed to compute policy exception key", "namespace", exception.GetNamespace(), "name", exception.GetName())
-			response = internal.RuleError(rule, ruleType, "failed to compute exception key", err)
-		} else {
-			logger.V(3).Info("policy rule skipped due to policy exception", "exception", key)
-			response = internal.RuleSkip(rule, ruleType, "rule skipped due to policy exception "+key)
-			response.Exception = exception
-		}
+	if err != nil {
+		logger.Error(err, "failed to match exceptions")
+		return nil
+	}
+	if exception == nil {
+		return nil
+	}
+	key, err := cache.MetaNamespaceKeyFunc(exception)
+	if err != nil {
+		logger.Error(err, "failed to compute policy exception key", "namespace", exception.GetNamespace(), "name", exception.GetName())
+		return engineapi.RuleError(rule.Name, ruleType, "failed to compute exception key", err)
+	} else {
+		logger.V(3).Info("policy rule skipped due to policy exception", "exception", key)
+		return engineapi.RuleSkip(rule.Name, ruleType, "rule skipped due to policy exception "+key).WithException(exception)
 	}
-	return response
 }
diff --git a/pkg/engine/forceMutate.go b/pkg/engine/forceMutate.go
index afde293a53..3bb9190d33 100644
--- a/pkg/engine/forceMutate.go
+++ b/pkg/engine/forceMutate.go
@@ -86,8 +86,8 @@ func applyForEachMutate(name string, foreach []kyvernov1.ForEachMutation, resour
 func applyPatches(name string, mergePatch apiextensions.JSON, jsonPatch string, resource unstructured.Unstructured, logger logr.Logger) (unstructured.Unstructured, error) {
 	patcher := mutate.NewPatcher(name, mergePatch, jsonPatch, resource, logger)
 	resp, mutatedResource := patcher.Patch()
-	if resp.Status != engineapi.RuleStatusPass {
-		return mutatedResource, fmt.Errorf("mutate status %q: %s", resp.Status, resp.Message)
+	if resp.Status() != engineapi.RuleStatusPass {
+		return mutatedResource, fmt.Errorf("mutate status %q: %s", resp.Status(), resp.Message())
 	}
 
 	return mutatedResource, nil
diff --git a/pkg/engine/handlers/handler.go b/pkg/engine/handlers/handler.go
index e155201f75..d30cda1b35 100644
--- a/pkg/engine/handlers/handler.go
+++ b/pkg/engine/handlers/handler.go
@@ -20,7 +20,23 @@ type Handler interface {
 	) (unstructured.Unstructured, []engineapi.RuleResponse)
 }
 
-func RuleResponses(rrs ...*engineapi.RuleResponse) []engineapi.RuleResponse {
+func WithError(rule kyvernov1.Rule, ruleType engineapi.RuleType, msg string, err error) []engineapi.RuleResponse {
+	return WithResponses(engineapi.RuleError(rule.Name, ruleType, msg, err))
+}
+
+func WithSkip(rule kyvernov1.Rule, ruleType engineapi.RuleType, msg string) []engineapi.RuleResponse {
+	return WithResponses(engineapi.RuleSkip(rule.Name, ruleType, msg))
+}
+
+func WithPass(rule kyvernov1.Rule, ruleType engineapi.RuleType, msg string) []engineapi.RuleResponse {
+	return WithResponses(engineapi.RulePass(rule.Name, ruleType, msg))
+}
+
+func WithFail(rule kyvernov1.Rule, ruleType engineapi.RuleType, msg string) []engineapi.RuleResponse {
+	return WithResponses(engineapi.RuleFail(rule.Name, ruleType, msg))
+}
+
+func WithResponses(rrs ...*engineapi.RuleResponse) []engineapi.RuleResponse {
 	var out []engineapi.RuleResponse
 	for _, rr := range rrs {
 		if rr != nil {
diff --git a/pkg/engine/handlers/mutation/common.go b/pkg/engine/handlers/mutation/common.go
index d4b71c105f..6b834a0145 100644
--- a/pkg/engine/handlers/mutation/common.go
+++ b/pkg/engine/handlers/mutation/common.go
@@ -134,15 +134,21 @@ func (f *forEachMutator) mutateElements(ctx context.Context, foreach kyvernov1.F
 }
 
 func buildRuleResponse(rule *kyvernov1.Rule, mutateResp *mutate.Response, info resourceInfo) *engineapi.RuleResponse {
-	resp := internal.RuleResponse(*rule, engineapi.Mutation, mutateResp.Message, mutateResp.Status)
-	if resp.Status == engineapi.RuleStatusPass {
-		resp.Patches = mutateResp.Patches
-		resp.Message = buildSuccessMessage(mutateResp.PatchedResource)
+	message := mutateResp.Message
+	if mutateResp.Status == engineapi.RuleStatusPass {
+		message = buildSuccessMessage(mutateResp.PatchedResource)
 	}
-	if len(rule.Mutation.Targets) != 0 {
-		resp.PatchedTarget = &mutateResp.PatchedResource
-		resp.PatchedTargetSubresourceName = info.subresource
-		resp.PatchedTargetParentResourceGVR = info.parentResourceGVR
+	resp := engineapi.NewRuleResponse(
+		rule.Name,
+		engineapi.Mutation,
+		message,
+		mutateResp.Status,
+	)
+	if mutateResp.Status == engineapi.RuleStatusPass {
+		resp = resp.WithPatches(mutateResp.Patches...)
+		if len(rule.Mutation.Targets) != 0 {
+			resp = resp.WithPatchedTarget(&mutateResp.PatchedResource, info.parentResourceGVR, info.subresource)
+		}
 	}
 	return resp
 }
diff --git a/pkg/engine/handlers/mutation/mutate_existing.go b/pkg/engine/handlers/mutation/mutate_existing.go
index 6bc321d6c5..ddc0b42d79 100644
--- a/pkg/engine/handlers/mutation/mutate_existing.go
+++ b/pkg/engine/handlers/mutation/mutate_existing.go
@@ -37,7 +37,7 @@ func (h mutateExistingHandler) Process(
 	logger.V(3).Info("processing mutate rule")
 	targets, err := loadTargets(h.client, rule.Mutation.Targets, policyContext, logger)
 	if err != nil {
-		rr := internal.RuleError(rule, engineapi.Mutation, "", err)
+		rr := engineapi.RuleError(rule.Name, engineapi.Mutation, "", err)
 		responses = append(responses, *rr)
 	}
 
@@ -52,19 +52,19 @@ func (h mutateExistingHandler) Process(
 		}
 		// load target specific context
 		if err := contextLoader(ctx, target.context, policyContext.JSONContext()); err != nil {
-			rr := internal.RuleError(rule, engineapi.Mutation, "failed to load context", err)
+			rr := engineapi.RuleError(rule.Name, engineapi.Mutation, "failed to load context", err)
 			responses = append(responses, *rr)
 			continue
 		}
 		// load target specific preconditions
 		preconditionsPassed, err := internal.CheckPreconditions(logger, policyContext.JSONContext(), target.preconditions)
 		if err != nil {
-			rr := internal.RuleError(rule, engineapi.Mutation, "failed to evaluate preconditions", err)
+			rr := engineapi.RuleError(rule.Name, engineapi.Mutation, "failed to evaluate preconditions", err)
 			responses = append(responses, *rr)
 			continue
 		}
 		if !preconditionsPassed {
-			rr := internal.RuleSkip(rule, engineapi.Mutation, "preconditions not met")
+			rr := engineapi.RuleSkip(rule.Name, engineapi.Mutation, "preconditions not met")
 			responses = append(responses, *rr)
 			continue
 		}
diff --git a/pkg/engine/handlers/mutation/mutate_image.go b/pkg/engine/handlers/mutation/mutate_image.go
index a28fe82e27..98037cc325 100644
--- a/pkg/engine/handlers/mutation/mutate_image.go
+++ b/pkg/engine/handlers/mutation/mutate_image.go
@@ -61,8 +61,8 @@ func (h mutateImageHandler) Process(
 	jsonContext := policyContext.JSONContext()
 	ruleCopy, err := substituteVariables(rule, jsonContext, logger)
 	if err != nil {
-		return resource, handlers.RuleResponses(
-			internal.RuleError(rule, engineapi.ImageVerify, "failed to substitute variables", err),
+		return resource, handlers.WithResponses(
+			engineapi.RuleError(rule.Name, engineapi.ImageVerify, "failed to substitute variables", err),
 		)
 	}
 	iv := internal.NewImageVerifier(logger, h.rclient, policyContext, *ruleCopy, h.ivm)
@@ -70,7 +70,7 @@ func (h mutateImageHandler) Process(
 	for _, imageVerify := range ruleCopy.VerifyImages {
 		engineResponses = append(engineResponses, iv.Verify(ctx, imageVerify, h.images, h.configuration)...)
 	}
-	return resource, handlers.RuleResponses(engineResponses...)
+	return resource, handlers.WithResponses(engineResponses...)
 }
 
 func substituteVariables(rule kyvernov1.Rule, ctx enginecontext.EvalInterface, logger logr.Logger) (*kyvernov1.Rule, error) {
diff --git a/pkg/engine/handlers/mutation/mutate_resource.go b/pkg/engine/handlers/mutation/mutate_resource.go
index 9493bd5ec6..4ceaaf0437 100644
--- a/pkg/engine/handlers/mutation/mutate_resource.go
+++ b/pkg/engine/handlers/mutation/mutate_resource.go
@@ -56,5 +56,5 @@ func (h mutateResourceHandler) Process(
 	if mutateResp == nil {
 		return resource, nil
 	}
-	return mutateResp.PatchedResource, handlers.RuleResponses(buildRuleResponse(&rule, mutateResp, resourceInfo))
+	return mutateResp.PatchedResource, handlers.WithResponses(buildRuleResponse(&rule, mutateResp, resourceInfo))
 }
diff --git a/pkg/engine/handlers/validation/validate_image.go b/pkg/engine/handlers/validation/validate_image.go
index 2e638e7d10..44466c2e49 100644
--- a/pkg/engine/handlers/validation/validate_image.go
+++ b/pkg/engine/handlers/validation/validate_image.go
@@ -9,7 +9,6 @@ import (
 	"github.com/kyverno/kyverno/pkg/config"
 	engineapi "github.com/kyverno/kyverno/pkg/engine/api"
 	"github.com/kyverno/kyverno/pkg/engine/handlers"
-	"github.com/kyverno/kyverno/pkg/engine/internal"
 	engineutils "github.com/kyverno/kyverno/pkg/engine/utils"
 	apiutils "github.com/kyverno/kyverno/pkg/utils/api"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -57,13 +56,13 @@ func (h validateImageHandler) Process(
 
 				logger.V(4).Info("validating image", "image", image)
 				if err := validateImage(policyContext, imageVerify, name, imageInfo, logger); err != nil {
-					return resource, handlers.RuleResponses(internal.RuleResponse(rule, engineapi.ImageVerify, err.Error(), engineapi.RuleStatusFail))
+					return resource, handlers.WithFail(rule, engineapi.ImageVerify, err.Error())
 				}
 			}
 		}
 	}
 	logger.V(4).Info("validated image", "rule", rule.Name)
-	return resource, handlers.RuleResponses(internal.RulePass(rule, engineapi.Validation, "image verified"))
+	return resource, handlers.WithPass(rule, engineapi.Validation, "image verified")
 }
 
 func validateImage(ctx engineapi.PolicyContext, imageVerify *kyvernov1.ImageVerification, name string, imageInfo apiutils.ImageInfo, log logr.Logger) error {
diff --git a/pkg/engine/handlers/validation/validate_manifest.go b/pkg/engine/handlers/validation/validate_manifest.go
index 7bc679c481..ec2dc5d8e9 100644
--- a/pkg/engine/handlers/validation/validate_manifest.go
+++ b/pkg/engine/handlers/validation/validate_manifest.go
@@ -62,13 +62,13 @@ func (h validateManifestHandler) Process(
 	verified, reason, err := h.verifyManifest(ctx, logger, policyContext, *rule.Validation.Manifests)
 	if err != nil {
 		logger.V(3).Info("verifyManifest return err", "error", err.Error())
-		return resource, handlers.RuleResponses(internal.RuleError(rule, engineapi.Validation, "error occurred during manifest verification", err))
+		return resource, handlers.WithError(rule, engineapi.Validation, "error occurred during manifest verification", err)
 	}
 	logger.V(3).Info("verifyManifest result", "verified", strconv.FormatBool(verified), "reason", reason)
 	if !verified {
-		return resource, handlers.RuleResponses(internal.RuleResponse(rule, engineapi.Validation, reason, engineapi.RuleStatusFail))
+		return resource, handlers.WithFail(rule, engineapi.Validation, reason)
 	}
-	return resource, handlers.RuleResponses(internal.RulePass(rule, engineapi.Validation, reason))
+	return resource, handlers.WithPass(rule, engineapi.Validation, reason)
 }
 
 func (h validateManifestHandler) verifyManifest(
diff --git a/pkg/engine/handlers/validation/validate_pss.go b/pkg/engine/handlers/validation/validate_pss.go
index 06a63c5905..3dff326587 100644
--- a/pkg/engine/handlers/validation/validate_pss.go
+++ b/pkg/engine/handlers/validation/validate_pss.go
@@ -9,7 +9,6 @@ import (
 	kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
 	engineapi "github.com/kyverno/kyverno/pkg/engine/api"
 	"github.com/kyverno/kyverno/pkg/engine/handlers"
-	"github.com/kyverno/kyverno/pkg/engine/internal"
 	"github.com/kyverno/kyverno/pkg/pss"
 	appsv1 "k8s.io/api/apps/v1"
 	batchv1 "k8s.io/api/batch/v1"
@@ -36,7 +35,7 @@ func (h validatePssHandler) Process(
 	podSecurity := rule.Validation.PodSecurity
 	podSpec, metadata, err := getSpec(resource)
 	if err != nil {
-		return resource, handlers.RuleResponses(internal.RuleError(rule, engineapi.Validation, "Error while getting new resource", err))
+		return resource, handlers.WithError(rule, engineapi.Validation, "Error while getting new resource", err)
 	}
 	pod := &corev1.Pod{
 		Spec:       *podSpec,
@@ -44,23 +43,23 @@ func (h validatePssHandler) Process(
 	}
 	allowed, pssChecks, err := pss.EvaluatePod(podSecurity, pod)
 	if err != nil {
-		return resource, handlers.RuleResponses(internal.RuleError(rule, engineapi.Validation, "failed to parse pod security api version", err))
+		return resource, handlers.WithError(rule, engineapi.Validation, "failed to parse pod security api version", err)
 	}
-	podSecurityChecks := &engineapi.PodSecurityChecks{
+	podSecurityChecks := engineapi.PodSecurityChecks{
 		Level:   podSecurity.Level,
 		Version: podSecurity.Version,
 		Checks:  pssChecks,
 	}
 	if allowed {
 		msg := fmt.Sprintf("Validation rule '%s' passed.", rule.Name)
-		rspn := internal.RulePass(rule, engineapi.Validation, msg)
-		rspn.PodSecurityChecks = podSecurityChecks
-		return resource, handlers.RuleResponses(rspn)
+		return resource, handlers.WithResponses(
+			engineapi.RulePass(rule.Name, engineapi.Validation, msg).WithPodSecurityChecks(podSecurityChecks),
+		)
 	} else {
 		msg := fmt.Sprintf(`Validation rule '%s' failed. It violates PodSecurity "%s:%s": %s`, rule.Name, podSecurity.Level, podSecurity.Version, pss.FormatChecksPrint(pssChecks))
-		rspn := internal.RuleResponse(rule, engineapi.Validation, msg, engineapi.RuleStatusFail)
-		rspn.PodSecurityChecks = podSecurityChecks
-		return resource, handlers.RuleResponses(rspn)
+		return resource, handlers.WithResponses(
+			engineapi.RuleFail(rule.Name, engineapi.Validation, msg).WithPodSecurityChecks(podSecurityChecks),
+		)
 	}
 }
 
diff --git a/pkg/engine/handlers/validation/validate_resource.go b/pkg/engine/handlers/validation/validate_resource.go
index 3a6ff7170b..7205c191f9 100644
--- a/pkg/engine/handlers/validation/validate_resource.go
+++ b/pkg/engine/handlers/validation/validate_resource.go
@@ -36,7 +36,7 @@ func (h validateResourceHandler) Process(
 	contextLoader engineapi.EngineContextLoader,
 ) (unstructured.Unstructured, []engineapi.RuleResponse) {
 	v := newValidator(logger, contextLoader, policyContext, rule)
-	return resource, handlers.RuleResponses(v.validate(ctx))
+	return resource, handlers.WithResponses(v.validate(ctx))
 }
 
 type validator struct {
@@ -99,14 +99,14 @@ func newForEachValidator(
 
 func (v *validator) validate(ctx context.Context) *engineapi.RuleResponse {
 	if err := v.loadContext(ctx); err != nil {
-		return internal.RuleError(v.rule, engineapi.Validation, "failed to load context", err)
+		return engineapi.RuleError(v.rule.Name, engineapi.Validation, "failed to load context", err)
 	}
 	preconditionsPassed, err := internal.CheckPreconditions(v.log, v.policyContext.JSONContext(), v.anyAllConditions)
 	if err != nil {
-		return internal.RuleError(v.rule, engineapi.Validation, "failed to evaluate preconditions", err)
+		return engineapi.RuleError(v.rule.Name, engineapi.Validation, "failed to evaluate preconditions", err)
 	}
 	if !preconditionsPassed {
-		return internal.RuleSkip(v.rule, engineapi.Validation, "preconditions not met")
+		return engineapi.RuleSkip(v.rule.Name, engineapi.Validation, "preconditions not met")
 	}
 
 	if v.deny != nil {
@@ -115,7 +115,7 @@ func (v *validator) validate(ctx context.Context) *engineapi.RuleResponse {
 
 	if v.pattern != nil || v.anyPattern != nil {
 		if err = v.substitutePatterns(); err != nil {
-			return internal.RuleError(v.rule, engineapi.Validation, "variable substitution failed", err)
+			return engineapi.RuleError(v.rule.Name, engineapi.Validation, "variable substitution failed", err)
 		}
 
 		ruleResponse := v.validateResourceWithRule()
@@ -140,7 +140,7 @@ func (v *validator) validateForEach(ctx context.Context) *engineapi.RuleResponse
 			continue
 		}
 		resp, count := v.validateElements(ctx, foreach, elements, foreach.ElementScope)
-		if resp.Status != engineapi.RuleStatusPass {
+		if resp.Status() != engineapi.RuleStatusPass {
 			return resp
 		}
 		applyCount += count
@@ -149,9 +149,9 @@ func (v *validator) validateForEach(ctx context.Context) *engineapi.RuleResponse
 		if v.forEach == nil {
 			return nil
 		}
-		return internal.RuleSkip(v.rule, engineapi.Validation, "rule skipped")
+		return engineapi.RuleSkip(v.rule.Name, engineapi.Validation, "rule skipped")
 	}
-	return internal.RulePass(v.rule, engineapi.Validation, "rule passed")
+	return engineapi.RulePass(v.rule.Name, engineapi.Validation, "rule passed")
 }
 
 func (v *validator) validateElements(ctx context.Context, foreach kyvernov1.ForEachValidation, elements []interface{}, elementScope *bool) (*engineapi.RuleResponse, int) {
@@ -168,38 +168,40 @@ func (v *validator) validateElements(ctx context.Context, foreach kyvernov1.ForE
 		policyContext := v.policyContext.Copy()
 		if err := engineutils.AddElementToContext(policyContext, element, index, v.nesting, elementScope); err != nil {
 			v.log.Error(err, "failed to add element to context")
-			return internal.RuleError(v.rule, engineapi.Validation, "failed to process foreach", err), applyCount
+			return engineapi.RuleError(v.rule.Name, engineapi.Validation, "failed to process foreach", err), applyCount
 		}
 
 		foreachValidator, err := newForEachValidator(foreach, v.contextLoader, v.nesting+1, v.rule, policyContext, v.log)
 		if err != nil {
 			v.log.Error(err, "failed to create foreach validator")
-			return internal.RuleError(v.rule, engineapi.Validation, "failed to create foreach validator", err), applyCount
+			return engineapi.RuleError(v.rule.Name, engineapi.Validation, "failed to create foreach validator", err), applyCount
 		}
 
 		r := foreachValidator.validate(ctx)
 		if r == nil {
 			v.log.V(2).Info("skip rule due to empty result")
 			continue
-		} else if r.Status == engineapi.RuleStatusSkip {
-			v.log.V(2).Info("skip rule", "reason", r.Message)
+		}
+		status := r.Status()
+		if status == engineapi.RuleStatusSkip {
+			v.log.V(2).Info("skip rule", "reason", r.Message())
 			continue
-		} else if r.Status != engineapi.RuleStatusPass {
-			if r.Status == engineapi.RuleStatusError {
+		} else if status != engineapi.RuleStatusPass {
+			if status == engineapi.RuleStatusError {
 				if index < len(elements)-1 {
 					continue
 				}
-				msg := fmt.Sprintf("validation failure: %v", r.Message)
-				return internal.RuleResponse(v.rule, engineapi.Validation, msg, r.Status), applyCount
+				msg := fmt.Sprintf("validation failure: %v", r.Message())
+				return engineapi.NewRuleResponse(v.rule.Name, engineapi.Validation, msg, status), applyCount
 			}
-			msg := fmt.Sprintf("validation failure: %v", r.Message)
-			return internal.RuleResponse(v.rule, engineapi.Validation, msg, r.Status), applyCount
+			msg := fmt.Sprintf("validation failure: %v", r.Message())
+			return engineapi.NewRuleResponse(v.rule.Name, engineapi.Validation, msg, status), applyCount
 		}
 
 		applyCount++
 	}
 
-	return internal.RulePass(v.rule, engineapi.Validation, ""), applyCount
+	return engineapi.RulePass(v.rule.Name, engineapi.Validation, ""), applyCount
 }
 
 func (v *validator) loadContext(ctx context.Context) error {
@@ -216,12 +218,12 @@ func (v *validator) loadContext(ctx context.Context) error {
 
 func (v *validator) validateDeny() *engineapi.RuleResponse {
 	if deny, err := internal.CheckDenyPreconditions(v.log, v.policyContext.JSONContext(), v.deny.GetAnyAllConditions()); err != nil {
-		return internal.RuleError(v.rule, engineapi.Validation, "failed to check deny preconditions", err)
+		return engineapi.RuleError(v.rule.Name, engineapi.Validation, "failed to check deny preconditions", err)
 	} else {
 		if deny {
-			return internal.RuleResponse(v.rule, engineapi.Validation, v.getDenyMessage(deny), engineapi.RuleStatusFail)
+			return engineapi.RuleFail(v.rule.Name, engineapi.Validation, v.getDenyMessage(deny))
 		}
-		return internal.RulePass(v.rule, engineapi.Validation, v.getDenyMessage(deny))
+		return engineapi.RulePass(v.rule.Name, engineapi.Validation, v.getDenyMessage(deny))
 	}
 }
 
@@ -267,22 +269,22 @@ func (v *validator) validatePatterns(resource unstructured.Unstructured) *engine
 				v.log.V(3).Info("validation error", "path", pe.Path, "error", err.Error())
 
 				if pe.Skip {
-					return internal.RuleSkip(v.rule, engineapi.Validation, pe.Error())
+					return engineapi.RuleSkip(v.rule.Name, engineapi.Validation, pe.Error())
 				}
 
 				if pe.Path == "" {
-					return internal.RuleResponse(v.rule, engineapi.Validation, v.buildErrorMessage(err, ""), engineapi.RuleStatusError)
+					return engineapi.RuleError(v.rule.Name, engineapi.Validation, v.buildErrorMessage(err, ""), nil)
 				}
 
-				return internal.RuleResponse(v.rule, engineapi.Validation, v.buildErrorMessage(err, pe.Path), engineapi.RuleStatusFail)
+				return engineapi.RuleFail(v.rule.Name, engineapi.Validation, v.buildErrorMessage(err, pe.Path))
 			}
 
-			return internal.RuleResponse(v.rule, engineapi.Validation, v.buildErrorMessage(err, pe.Path), engineapi.RuleStatusError)
+			return engineapi.RuleError(v.rule.Name, engineapi.Validation, v.buildErrorMessage(err, pe.Path), nil)
 		}
 
 		v.log.V(4).Info("successfully processed rule")
 		msg := fmt.Sprintf("validation rule '%s' passed.", v.rule.Name)
-		return internal.RulePass(v.rule, engineapi.Validation, msg)
+		return engineapi.RulePass(v.rule.Name, engineapi.Validation, msg)
 	}
 
 	if v.anyPattern != nil {
@@ -292,14 +294,14 @@ func (v *validator) validatePatterns(resource unstructured.Unstructured) *engine
 
 		anyPatterns, err := deserializeAnyPattern(v.anyPattern)
 		if err != nil {
-			return internal.RuleError(v.rule, engineapi.Validation, "failed to deserialize anyPattern, expected type array", err)
+			return engineapi.RuleError(v.rule.Name, engineapi.Validation, "failed to deserialize anyPattern, expected type array", err)
 		}
 
 		for idx, pattern := range anyPatterns {
 			err := validate.MatchPattern(v.log, resource.Object, pattern)
 			if err == nil {
 				msg := fmt.Sprintf("validation rule '%s' anyPattern[%d] passed.", v.rule.Name, idx)
-				return internal.RulePass(v.rule, engineapi.Validation, msg)
+				return engineapi.RulePass(v.rule.Name, engineapi.Validation, msg)
 			}
 
 			if pe, ok := err.(*validate.PatternError); ok {
@@ -327,7 +329,7 @@ func (v *validator) validatePatterns(resource unstructured.Unstructured) *engine
 				errorStr = append(errorStr, err.Error())
 			}
 			v.log.V(4).Info(fmt.Sprintf("Validation rule '%s' skipped. %s", v.rule.Name, errorStr))
-			return internal.RuleSkip(v.rule, engineapi.Validation, strings.Join(errorStr, " "))
+			return engineapi.RuleSkip(v.rule.Name, engineapi.Validation, strings.Join(errorStr, " "))
 		} else if len(failedAnyPatternsErrors) > 0 {
 			var errorStr []string
 			for _, err := range failedAnyPatternsErrors {
@@ -336,11 +338,11 @@ func (v *validator) validatePatterns(resource unstructured.Unstructured) *engine
 
 			v.log.V(4).Info(fmt.Sprintf("Validation rule '%s' failed. %s", v.rule.Name, errorStr))
 			msg := buildAnyPatternErrorMessage(v.rule, errorStr)
-			return internal.RuleResponse(v.rule, engineapi.Validation, msg, engineapi.RuleStatusFail)
+			return engineapi.RuleFail(v.rule.Name, engineapi.Validation, msg)
 		}
 	}
 
-	return internal.RulePass(v.rule, engineapi.Validation, v.rule.Validation.Message)
+	return engineapi.RulePass(v.rule.Name, engineapi.Validation, v.rule.Validation.Message)
 }
 
 func deserializeAnyPattern(anyPattern apiextensions.JSON) ([]interface{}, error) {
diff --git a/pkg/engine/image_verify.go b/pkg/engine/image_verify.go
index 8424f16e7b..513d2b03dc 100644
--- a/pkg/engine/image_verify.go
+++ b/pkg/engine/image_verify.go
@@ -53,10 +53,9 @@ func (e *engine) verifyAndPatchImages(
 			engineapi.ImageVerify,
 		)
 		matchedResource = resource
+		endTime := time.Now()
 		for _, ruleResp := range ruleResp {
-			ruleResp := ruleResp
-			internal.AddRuleResponse(&resp, &ruleResp, startTime)
-			logger.V(4).Info("finished processing rule", "processingTime", ruleResp.Stats.ProcessingTime.String())
+			resp.Add(startTime, endTime, ruleResp)
 		}
 		if applyRules == kyvernov1.ApplyOne && resp.Stats.RulesAppliedCount > 0 {
 			break
diff --git a/pkg/engine/image_verify_test.go b/pkg/engine/image_verify_test.go
index 4e2c189df5..e69188f1e5 100644
--- a/pkg/engine/image_verify_test.go
+++ b/pkg/engine/image_verify_test.go
@@ -194,9 +194,9 @@ func Test_CosignMockAttest(t *testing.T) {
 
 	er, ivm := testVerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), nil, policyContext, cfg)
 	assert.Equal(t, len(er.PolicyResponse.Rules), 1)
-	assert.Equal(t, er.PolicyResponse.Rules[0].Status, engineapi.RuleStatusPass,
+	assert.Equal(t, er.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusPass,
 		fmt.Sprintf("expected: %v, got: %v, failure: %v",
-			engineapi.RuleStatusPass, er.PolicyResponse.Rules[0].Status, er.PolicyResponse.Rules[0].Message))
+			engineapi.RuleStatusPass, er.PolicyResponse.Rules[0].Status(), er.PolicyResponse.Rules[0].Message()))
 	assert.Equal(t, ivm.IsEmpty(), false)
 	assert.Equal(t, ivm.IsVerified("ghcr.io/jimbugwadia/pause2:latest"), true)
 }
@@ -208,7 +208,7 @@ func Test_CosignMockAttest_fail(t *testing.T) {
 
 	er, _ := testVerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), nil, policyContext, cfg)
 	assert.Equal(t, len(er.PolicyResponse.Rules), 1)
-	assert.Equal(t, er.PolicyResponse.Rules[0].Status, engineapi.RuleStatusFail)
+	assert.Equal(t, er.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusFail)
 }
 
 func buildContext(t *testing.T, policy, resource string, oldResource string) *PolicyContext {
@@ -465,7 +465,7 @@ func Test_ConfigMapMissingFailure(t *testing.T) {
 	cosign.ClearMock()
 	resp, _ := testVerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), resolver, policyContext, cfg)
 	assert.Equal(t, len(resp.PolicyResponse.Rules), 1)
-	assert.Equal(t, resp.PolicyResponse.Rules[0].Status, engineapi.RuleStatusError, resp.PolicyResponse.Rules[0].Message)
+	assert.Equal(t, resp.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusError, resp.PolicyResponse.Rules[0].Message())
 }
 
 func Test_SignatureGoodSigned(t *testing.T) {
@@ -474,9 +474,9 @@ func Test_SignatureGoodSigned(t *testing.T) {
 	cosign.ClearMock()
 	engineResp, _ := testVerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), nil, policyContext, cfg)
 	assert.Equal(t, len(engineResp.PolicyResponse.Rules), 1)
-	assert.Equal(t, engineResp.PolicyResponse.Rules[0].Status, engineapi.RuleStatusPass, engineResp.PolicyResponse.Rules[0].Message)
-	assert.Equal(t, len(engineResp.PolicyResponse.Rules[0].Patches), 1)
-	patch := engineResp.PolicyResponse.Rules[0].Patches[0]
+	assert.Equal(t, engineResp.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusPass, engineResp.PolicyResponse.Rules[0].Message())
+	assert.Equal(t, len(engineResp.PolicyResponse.Rules[0].Patches()), 1)
+	patch := engineResp.PolicyResponse.Rules[0].Patches()[0]
 	assert.Equal(t, string(patch), "{\"op\":\"replace\",\"path\":\"/spec/containers/0/image\",\"value\":\"ghcr.io/kyverno/test-verify-image:signed@sha256:b31bfb4d0213f254d361e0079deaaebefa4f82ba7aa76ef82e90b4935ad5b105\"}")
 }
 
@@ -486,7 +486,7 @@ func Test_SignatureUnsigned(t *testing.T) {
 	policyContext := buildContext(t, testSampleSingleKeyPolicy, unsigned, "")
 	engineResp, _ := testVerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), nil, policyContext, cfg)
 	assert.Equal(t, len(engineResp.PolicyResponse.Rules), 1)
-	assert.Equal(t, engineResp.PolicyResponse.Rules[0].Status, engineapi.RuleStatusFail, engineResp.PolicyResponse.Rules[0].Message)
+	assert.Equal(t, engineResp.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusFail, engineResp.PolicyResponse.Rules[0].Message())
 }
 
 func Test_SignatureWrongKey(t *testing.T) {
@@ -495,7 +495,7 @@ func Test_SignatureWrongKey(t *testing.T) {
 	policyContext := buildContext(t, testSampleSingleKeyPolicy, otherKey, "")
 	engineResp, _ := testVerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), nil, policyContext, cfg)
 	assert.Equal(t, len(engineResp.PolicyResponse.Rules), 1)
-	assert.Equal(t, engineResp.PolicyResponse.Rules[0].Status, engineapi.RuleStatusFail, engineResp.PolicyResponse.Rules[0].Message)
+	assert.Equal(t, engineResp.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusFail, engineResp.PolicyResponse.Rules[0].Message())
 }
 
 func Test_SignaturesMultiKey(t *testing.T) {
@@ -506,7 +506,7 @@ func Test_SignaturesMultiKey(t *testing.T) {
 	policyContext := buildContext(t, policy, testSampleResource, "")
 	engineResp, _ := testVerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), nil, policyContext, cfg)
 	assert.Equal(t, len(engineResp.PolicyResponse.Rules), 1)
-	assert.Equal(t, engineResp.PolicyResponse.Rules[0].Status, engineapi.RuleStatusPass, engineResp.PolicyResponse.Rules[0].Message)
+	assert.Equal(t, engineResp.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusPass, engineResp.PolicyResponse.Rules[0].Message())
 }
 
 func Test_SignaturesMultiKeyFail(t *testing.T) {
@@ -516,7 +516,7 @@ func Test_SignaturesMultiKeyFail(t *testing.T) {
 	policyContext := buildContext(t, policy, testSampleResource, "")
 	engineResp, _ := testVerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), nil, policyContext, cfg)
 	assert.Equal(t, len(engineResp.PolicyResponse.Rules), 1)
-	assert.Equal(t, engineResp.PolicyResponse.Rules[0].Status, engineapi.RuleStatusFail, engineResp.PolicyResponse.Rules[0].Message)
+	assert.Equal(t, engineResp.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusFail, engineResp.PolicyResponse.Rules[0].Message())
 }
 
 func Test_SignaturesMultiKeyOneGoodKey(t *testing.T) {
@@ -527,7 +527,7 @@ func Test_SignaturesMultiKeyOneGoodKey(t *testing.T) {
 	policyContext := buildContext(t, policy, testSampleResource, "")
 	engineResp, _ := testVerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), nil, policyContext, cfg)
 	assert.Equal(t, len(engineResp.PolicyResponse.Rules), 1)
-	assert.Equal(t, engineResp.PolicyResponse.Rules[0].Status, engineapi.RuleStatusPass, engineResp.PolicyResponse.Rules[0].Message)
+	assert.Equal(t, engineResp.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusPass, engineResp.PolicyResponse.Rules[0].Message())
 }
 
 func Test_SignaturesMultiKeyZeroGoodKey(t *testing.T) {
@@ -538,7 +538,7 @@ func Test_SignaturesMultiKeyZeroGoodKey(t *testing.T) {
 	policyContext := buildContext(t, policy, testSampleResource, "")
 	resp, _ := testVerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), nil, policyContext, cfg)
 	assert.Equal(t, len(resp.PolicyResponse.Rules), 1)
-	assert.Equal(t, resp.PolicyResponse.Rules[0].Status, engineapi.RuleStatusFail, resp.PolicyResponse.Rules[0].Message)
+	assert.Equal(t, resp.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusFail, resp.PolicyResponse.Rules[0].Message())
 }
 
 func Test_RuleSelectorImageVerify(t *testing.T) {
@@ -554,14 +554,14 @@ func Test_RuleSelectorImageVerify(t *testing.T) {
 
 	resp, _ := testVerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), nil, policyContext, cfg)
 	assert.Equal(t, len(resp.PolicyResponse.Rules), 2)
-	assert.Equal(t, resp.PolicyResponse.Rules[0].Status, engineapi.RuleStatusPass, resp.PolicyResponse.Rules[0].Message)
-	assert.Equal(t, resp.PolicyResponse.Rules[1].Status, engineapi.RuleStatusFail, resp.PolicyResponse.Rules[1].Message)
+	assert.Equal(t, resp.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusPass, resp.PolicyResponse.Rules[0].Message())
+	assert.Equal(t, resp.PolicyResponse.Rules[1].Status(), engineapi.RuleStatusFail, resp.PolicyResponse.Rules[1].Message())
 
 	applyOne := kyverno.ApplyOne
 	spec.ApplyRules = &applyOne
 	resp, _ = testVerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), nil, policyContext, cfg)
 	assert.Equal(t, len(resp.PolicyResponse.Rules), 1)
-	assert.Equal(t, resp.PolicyResponse.Rules[0].Status, engineapi.RuleStatusPass, resp.PolicyResponse.Rules[0].Message)
+	assert.Equal(t, resp.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusPass, resp.PolicyResponse.Rules[0].Message())
 }
 
 func newStaticKeyRule(name, imageReference, key string) *kyverno.Rule {
@@ -665,7 +665,7 @@ func Test_NestedAttestors(t *testing.T) {
 	policyContext := buildContext(t, policy, testSampleResource, "")
 	err, _ := testVerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), nil, policyContext, cfg)
 	assert.Equal(t, len(err.PolicyResponse.Rules), 1)
-	assert.Equal(t, err.PolicyResponse.Rules[0].Status, engineapi.RuleStatusPass)
+	assert.Equal(t, err.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusPass)
 
 	policy = strings.Replace(testNestedAttestorPolicy, "KEY1", testVerifyImageKey, -1)
 	policy = strings.Replace(policy, "KEY2", testOtherKey, -1)
@@ -673,7 +673,7 @@ func Test_NestedAttestors(t *testing.T) {
 	policyContext = buildContext(t, policy, testSampleResource, "")
 	err, _ = testVerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), nil, policyContext, cfg)
 	assert.Equal(t, len(err.PolicyResponse.Rules), 1)
-	assert.Equal(t, err.PolicyResponse.Rules[0].Status, engineapi.RuleStatusFail)
+	assert.Equal(t, err.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusFail)
 
 	policy = strings.Replace(testNestedAttestorPolicy, "KEY1", testVerifyImageKey, -1)
 	policy = strings.Replace(policy, "KEY2", testOtherKey, -1)
@@ -681,7 +681,7 @@ func Test_NestedAttestors(t *testing.T) {
 	policyContext = buildContext(t, policy, testSampleResource, "")
 	err, _ = testVerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), nil, policyContext, cfg)
 	assert.Equal(t, len(err.PolicyResponse.Rules), 1)
-	assert.Equal(t, err.PolicyResponse.Rules[0].Status, engineapi.RuleStatusPass)
+	assert.Equal(t, err.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusPass)
 }
 
 func Test_ExpandKeys(t *testing.T) {
@@ -773,7 +773,7 @@ func Test_MarkImageVerified(t *testing.T) {
 
 	engineResponse, verifiedImages := testVerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), nil, policyContext, cfg)
 	assert.Equal(t, len(engineResponse.PolicyResponse.Rules), 1)
-	assert.Equal(t, engineResponse.PolicyResponse.Rules[0].Status, engineapi.RuleStatusPass)
+	assert.Equal(t, engineResponse.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusPass)
 
 	assert.Assert(t, verifiedImages.Data != nil)
 	assert.Equal(t, len(verifiedImages.Data), 1)
@@ -864,7 +864,7 @@ func Test_ParsePEMDelimited(t *testing.T) {
 
 	engineResponse, verifiedImages := testVerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), nil, policyContext, cfg)
 	assert.Equal(t, len(engineResponse.PolicyResponse.Rules), 1)
-	assert.Equal(t, engineResponse.PolicyResponse.Rules[0].Status, engineapi.RuleStatusPass)
+	assert.Equal(t, engineResponse.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusPass)
 
 	assert.Assert(t, verifiedImages.Data != nil)
 	assert.Equal(t, len(verifiedImages.Data), 1)
diff --git a/pkg/engine/internal/imageverifier.go b/pkg/engine/internal/imageverifier.go
index 136f204e1f..2e0d46dac4 100644
--- a/pkg/engine/internal/imageverifier.go
+++ b/pkg/engine/internal/imageverifier.go
@@ -197,7 +197,7 @@ func (iv *ImageVerifier) Verify(
 		if HasImageVerifiedAnnotationChanged(iv.policyContext, iv.logger) {
 			msg := engineapi.ImageVerifyAnnotationKey + " annotation cannot be changed"
 			iv.logger.Info("image verification error", "reason", msg)
-			responses = append(responses, RuleResponse(iv.rule, engineapi.ImageVerify, msg, engineapi.RuleStatusFail))
+			responses = append(responses, engineapi.RuleFail(iv.rule.Name, engineapi.ImageVerify, msg))
 			continue
 		}
 
@@ -219,12 +219,12 @@ func (iv *ImageVerifier) Verify(
 		if imageVerify.MutateDigest {
 			patch, retrievedDigest, err := iv.handleMutateDigest(ctx, digest, imageInfo)
 			if err != nil {
-				responses = append(responses, RuleError(iv.rule, engineapi.ImageVerify, "failed to update digest", err))
+				responses = append(responses, engineapi.RuleError(iv.rule.Name, engineapi.ImageVerify, "failed to update digest", err))
 			} else if patch != nil {
 				if ruleResp == nil {
-					ruleResp = RulePass(iv.rule, engineapi.ImageVerify, "mutated image digest")
+					ruleResp = engineapi.RulePass(iv.rule.Name, engineapi.ImageVerify, "mutated image digest")
 				}
-				ruleResp.Patches = append(ruleResp.Patches, patch)
+				ruleResp = ruleResp.WithPatches(patch)
 				imageInfo.Digest = retrievedDigest
 				image = imageInfo.String()
 			}
@@ -232,7 +232,7 @@ func (iv *ImageVerifier) Verify(
 
 		if ruleResp != nil {
 			if len(imageVerify.Attestors) > 0 || len(imageVerify.Attestations) > 0 {
-				iv.ivm.Add(image, ruleResp.Status == engineapi.RuleStatusPass)
+				iv.ivm.Add(image, ruleResp.Status() == engineapi.RuleStatusPass)
 			}
 			responses = append(responses, ruleResp)
 		}
@@ -253,14 +253,14 @@ func (iv *ImageVerifier) verifyImage(
 	iv.logger.V(2).Info("verifying image signatures", "image", image, "attestors", len(imageVerify.Attestors), "attestations", len(imageVerify.Attestations))
 	if err := iv.policyContext.JSONContext().AddImageInfo(imageInfo, cfg); err != nil {
 		iv.logger.Error(err, "failed to add image to context")
-		return RuleError(iv.rule, engineapi.ImageVerify, fmt.Sprintf("failed to add image to context %s", image), err), ""
+		return engineapi.RuleError(iv.rule.Name, engineapi.ImageVerify, fmt.Sprintf("failed to add image to context %s", image), err), ""
 	}
 	if len(imageVerify.Attestors) > 0 {
 		if !matchImageReferences(imageVerify.ImageReferences, image) {
 			return nil, ""
 		}
 		ruleResp, cosignResp := iv.verifyAttestors(ctx, imageVerify.Attestors, imageVerify, imageInfo, "")
-		if ruleResp.Status != engineapi.RuleStatusPass {
+		if ruleResp.Status() != engineapi.RuleStatusPass {
 			return ruleResp, ""
 		}
 		if len(imageVerify.Attestations) == 0 {
@@ -299,10 +299,10 @@ func (iv *ImageVerifier) verifyAttestors(
 		}
 	}
 	if cosignResponse == nil {
-		return RuleError(iv.rule, engineapi.ImageVerify, "invalid response", fmt.Errorf("nil")), nil
+		return engineapi.RuleError(iv.rule.Name, engineapi.ImageVerify, "invalid response", fmt.Errorf("nil")), nil
 	}
 	msg := fmt.Sprintf("verified image signatures for %s", image)
-	return RulePass(iv.rule, engineapi.ImageVerify, msg), cosignResponse
+	return engineapi.RulePass(iv.rule.Name, engineapi.ImageVerify, msg), cosignResponse
 }
 
 // handle registry network errors as a rule error (instead of a policy failure)
@@ -310,9 +310,9 @@ func (iv *ImageVerifier) handleRegistryErrors(image string, err error) *engineap
 	msg := fmt.Sprintf("failed to verify image %s: %s", image, err.Error())
 	var netErr *net.OpError
 	if errors.As(err, &netErr) {
-		return RuleError(iv.rule, engineapi.ImageVerify, fmt.Sprintf("failed to verify image %s", image), err)
+		return engineapi.RuleError(iv.rule.Name, engineapi.ImageVerify, fmt.Sprintf("failed to verify image %s", image), err)
 	}
-	return RuleResponse(iv.rule, engineapi.ImageVerify, msg, engineapi.RuleStatusFail)
+	return engineapi.RuleFail(iv.rule.Name, engineapi.ImageVerify, msg)
 }
 
 func (iv *ImageVerifier) verifyAttestations(
@@ -326,7 +326,7 @@ func (iv *ImageVerifier) verifyAttestations(
 		path := fmt.Sprintf(".attestations[%d]", i)
 
 		if attestation.PredicateType == "" {
-			return RuleResponse(iv.rule, engineapi.ImageVerify, path+": missing predicateType", engineapi.RuleStatusFail), ""
+			return engineapi.RuleFail(iv.rule.Name, engineapi.ImageVerify, path+": missing predicateType"), ""
 		}
 
 		if len(attestation.Attestors) == 0 {
@@ -356,7 +356,7 @@ func (iv *ImageVerifier) verifyAttestations(
 				attestationError = iv.verifyAttestation(cosignResp.Statements, attestation, imageInfo)
 				if attestationError != nil {
 					attestationError = fmt.Errorf("%s: %w", entryPath+subPath, attestationError)
-					return RuleResponse(iv.rule, engineapi.ImageVerify, attestationError.Error(), engineapi.RuleStatusFail), ""
+					return engineapi.RuleFail(iv.rule.Name, engineapi.ImageVerify, attestationError.Error()), ""
 				}
 
 				verifiedCount++
@@ -368,7 +368,7 @@ func (iv *ImageVerifier) verifyAttestations(
 
 			if verifiedCount < requiredCount {
 				msg := fmt.Sprintf("image attestations verification failed, verifiedCount: %v, requiredCount: %v", verifiedCount, requiredCount)
-				return RuleResponse(iv.rule, engineapi.ImageVerify, msg, engineapi.RuleStatusFail), ""
+				return engineapi.RuleFail(iv.rule.Name, engineapi.ImageVerify, msg), ""
 			}
 		}
 
@@ -377,7 +377,7 @@ func (iv *ImageVerifier) verifyAttestations(
 
 	msg := fmt.Sprintf("verified image attestations for %s", image)
 	iv.logger.V(2).Info(msg)
-	return RulePass(iv.rule, engineapi.ImageVerify, msg), imageInfo.Digest
+	return engineapi.RulePass(iv.rule.Name, engineapi.ImageVerify, msg), imageInfo.Digest
 }
 
 func (iv *ImageVerifier) verifyAttestorSet(
diff --git a/pkg/engine/internal/response.go b/pkg/engine/internal/response.go
deleted file mode 100644
index 38fd9e5041..0000000000
--- a/pkg/engine/internal/response.go
+++ /dev/null
@@ -1,56 +0,0 @@
-package internal
-
-import (
-	"fmt"
-	"time"
-
-	kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
-	engineapi "github.com/kyverno/kyverno/pkg/engine/api"
-)
-
-func RuleError(rule kyvernov1.Rule, ruleType engineapi.RuleType, msg string, err error) *engineapi.RuleResponse {
-	return RuleResponse(rule, ruleType, fmt.Sprintf("%s: %s", msg, err.Error()), engineapi.RuleStatusError)
-}
-
-func RuleSkip(rule kyvernov1.Rule, ruleType engineapi.RuleType, msg string) *engineapi.RuleResponse {
-	return RuleResponse(rule, ruleType, msg, engineapi.RuleStatusSkip)
-}
-
-func RulePass(rule kyvernov1.Rule, ruleType engineapi.RuleType, msg string) *engineapi.RuleResponse {
-	return RuleResponse(rule, ruleType, msg, engineapi.RuleStatusPass)
-}
-
-func RuleResponse(rule kyvernov1.Rule, ruleType engineapi.RuleType, msg string, status engineapi.RuleStatus) *engineapi.RuleResponse {
-	resp := &engineapi.RuleResponse{
-		Name:    rule.Name,
-		Type:    ruleType,
-		Message: msg,
-		Status:  status,
-	}
-	return resp
-}
-
-func AddRuleResponse(resp *engineapi.PolicyResponse, ruleResp *engineapi.RuleResponse, startTime time.Time) {
-	ruleResp.Stats.ProcessingTime = time.Since(startTime)
-	ruleResp.Stats.Timestamp = startTime.Unix()
-	resp.Rules = append(resp.Rules, *ruleResp)
-	if ruleResp.Status == engineapi.RuleStatusPass || ruleResp.Status == engineapi.RuleStatusFail {
-		resp.Stats.RulesAppliedCount++
-	} else if ruleResp.Status == engineapi.RuleStatusError {
-		resp.Stats.RulesErrorCount++
-	}
-}
-
-func BuildResponse(ctx engineapi.PolicyContext, resp *engineapi.EngineResponse, startTime time.Time) *engineapi.EngineResponse {
-	if resp.PatchedResource.Object == nil {
-		// for delete requests patched resource will be oldResource since newResource is empty
-		resource := ctx.NewResource()
-		if resource.Object == nil {
-			resource = ctx.OldResource()
-		}
-		resp.PatchedResource = resource
-	}
-	resp.Stats.ProcessingTime = time.Since(startTime)
-	resp.Stats.Timestamp = startTime.Unix()
-	return resp
-}
diff --git a/pkg/engine/metrics.go b/pkg/engine/metrics.go
index 641ab08834..2e102d449a 100644
--- a/pkg/engine/metrics.go
+++ b/pkg/engine/metrics.go
@@ -35,10 +35,10 @@ func (e *engine) reportMetrics(
 		resourceKind := resourceSpec.GetKind()
 		resourceNamespace := resourceSpec.GetNamespace()
 		for _, rule := range response.PolicyResponse.Rules {
-			ruleName := rule.Name
+			ruleName := rule.Name()
 			ruleType := metrics.ParseRuleTypeFromEngineRuleResponse(rule)
 			var ruleResult metrics.RuleResult
-			switch rule.Status {
+			switch rule.Status() {
 			case engineapi.RuleStatusPass:
 				ruleResult = metrics.Pass
 			case engineapi.RuleStatusFail:
@@ -88,7 +88,7 @@ func (e *engine) reportMetrics(
 					attribute.String("rule_type", string(ruleType)),
 					attribute.String("rule_execution_cause", string(executionCause)),
 				}
-				e.durationHistogram.Record(ctx, rule.Stats.ProcessingTime.Seconds(), commonLabels...)
+				e.durationHistogram.Record(ctx, rule.Stats().ProcessingTime.Seconds(), commonLabels...)
 			}
 		}
 	}
diff --git a/pkg/engine/mutate/mutation.go b/pkg/engine/mutate/mutation.go
index 5186cb13f2..fe997dbdc6 100644
--- a/pkg/engine/mutate/mutation.go
+++ b/pkg/engine/mutate/mutation.go
@@ -48,11 +48,11 @@ func Mutate(rule *kyvernov1.Rule, ctx context.Interface, resource unstructured.U
 	}
 
 	resp, patchedResource := patcher.Patch()
-	if resp.Status != engineapi.RuleStatusPass {
-		return NewResponse(resp.Status, resource, nil, resp.Message)
+	if resp.Status() != engineapi.RuleStatusPass {
+		return NewResponse(resp.Status(), resource, nil, resp.Message())
 	}
 
-	if resp.Patches == nil {
+	if resp.Patches() == nil {
 		return NewResponse(engineapi.RuleStatusSkip, resource, nil, "no patches applied")
 	}
 
@@ -66,7 +66,7 @@ func Mutate(rule *kyvernov1.Rule, ctx context.Interface, resource unstructured.U
 		}
 	}
 
-	return NewResponse(engineapi.RuleStatusPass, patchedResource, resp.Patches, resp.Message)
+	return NewResponse(engineapi.RuleStatusPass, patchedResource, resp.Patches(), resp.Message())
 }
 
 func ForEach(name string, foreach kyvernov1.ForEachMutation, policyContext engineapi.PolicyContext, resource unstructured.Unstructured, element interface{}, logger logr.Logger) *Response {
@@ -82,11 +82,11 @@ func ForEach(name string, foreach kyvernov1.ForEachMutation, policyContext engin
 	}
 
 	resp, patchedResource := patcher.Patch()
-	if resp.Status != engineapi.RuleStatusPass {
-		return NewResponse(resp.Status, unstructured.Unstructured{}, nil, resp.Message)
+	if resp.Status() != engineapi.RuleStatusPass {
+		return NewResponse(resp.Status(), unstructured.Unstructured{}, nil, resp.Message())
 	}
 
-	if resp.Patches == nil {
+	if resp.Patches() == nil {
 		return NewResponse(engineapi.RuleStatusSkip, unstructured.Unstructured{}, nil, "no patches applied")
 	}
 
@@ -94,7 +94,7 @@ func ForEach(name string, foreach kyvernov1.ForEachMutation, policyContext engin
 		return NewErrorResponse("failed to update patched resource in the JSON context", err)
 	}
 
-	return NewResponse(engineapi.RuleStatusPass, patchedResource, resp.Patches, resp.Message)
+	return NewResponse(engineapi.RuleStatusPass, patchedResource, resp.Patches(), resp.Message())
 }
 
 func substituteAllInForEach(fe kyvernov1.ForEachMutation, ctx context.Interface, logger logr.Logger) (*kyvernov1.ForEachMutation, error) {
diff --git a/pkg/engine/mutate/mutation_test.go b/pkg/engine/mutate/mutation_test.go
index 223b4186b0..280e0b28a8 100644
--- a/pkg/engine/mutate/mutation_test.go
+++ b/pkg/engine/mutate/mutation_test.go
@@ -48,20 +48,10 @@ const endpointsDocument string = `{
 
 func applyPatches(rule *types.Rule, resource unstructured.Unstructured) (*engineapi.RuleResponse, unstructured.Unstructured) {
 	mutateResp := Mutate(rule, context.NewContext(), resource, logr.Discard())
-
 	if mutateResp.Status != engineapi.RuleStatusPass {
-		return &engineapi.RuleResponse{
-			Type:    engineapi.Mutation,
-			Status:  mutateResp.Status,
-			Message: mutateResp.Message,
-		}, resource
+		return engineapi.NewRuleResponse("", engineapi.Mutation, mutateResp.Message, mutateResp.Status), resource
 	}
-
-	return &engineapi.RuleResponse{
-		Type:    engineapi.Mutation,
-		Status:  engineapi.RuleStatusPass,
-		Patches: mutateResp.Patches,
-	}, mutateResp.PatchedResource
+	return engineapi.RulePass("", engineapi.Mutation, mutateResp.Message).WithPatches(mutateResp.Patches...), mutateResp.PatchedResource
 }
 
 func TestProcessPatches_EmptyPatches(t *testing.T) {
@@ -72,8 +62,8 @@ func TestProcessPatches_EmptyPatches(t *testing.T) {
 	}
 
 	rr, _ := applyPatches(emptyRule, *resourceUnstructured)
-	assert.Equal(t, rr.Status, engineapi.RuleStatusError)
-	assert.Assert(t, len(rr.Patches) == 0)
+	assert.Equal(t, rr.Status(), engineapi.RuleStatusError)
+	assert.Assert(t, len(rr.Patches()) == 0)
 }
 
 func makeAddIsMutatedLabelPatch() jsonPatch {
@@ -106,15 +96,15 @@ func makeRuleWithPatches(t *testing.T, patches []jsonPatch) *types.Rule {
 func TestProcessPatches_EmptyDocument(t *testing.T) {
 	rule := makeRuleWithPatch(t, makeAddIsMutatedLabelPatch())
 	rr, _ := applyPatches(rule, unstructured.Unstructured{})
-	assert.Equal(t, rr.Status, engineapi.RuleStatusFail)
-	assert.Assert(t, len(rr.Patches) == 0)
+	assert.Equal(t, rr.Status(), engineapi.RuleStatusFail)
+	assert.Assert(t, len(rr.Patches()) == 0)
 }
 
 func TestProcessPatches_AllEmpty(t *testing.T) {
 	emptyRule := &types.Rule{}
 	rr, _ := applyPatches(emptyRule, unstructured.Unstructured{})
-	assert.Equal(t, rr.Status, engineapi.RuleStatusError)
-	assert.Assert(t, len(rr.Patches) == 0)
+	assert.Equal(t, rr.Status(), engineapi.RuleStatusError)
+	assert.Assert(t, len(rr.Patches()) == 0)
 }
 
 func TestProcessPatches_AddPathDoesntExist(t *testing.T) {
@@ -126,8 +116,8 @@ func TestProcessPatches_AddPathDoesntExist(t *testing.T) {
 		t.Error(err)
 	}
 	rr, _ := applyPatches(rule, *resourceUnstructured)
-	assert.Equal(t, rr.Status, engineapi.RuleStatusSkip)
-	assert.Assert(t, len(rr.Patches) == 0)
+	assert.Equal(t, rr.Status(), engineapi.RuleStatusSkip)
+	assert.Assert(t, len(rr.Patches()) == 0)
 }
 
 func TestProcessPatches_RemovePathDoesntExist(t *testing.T) {
@@ -138,8 +128,8 @@ func TestProcessPatches_RemovePathDoesntExist(t *testing.T) {
 		t.Error(err)
 	}
 	rr, _ := applyPatches(rule, *resourceUnstructured)
-	assert.Equal(t, rr.Status, engineapi.RuleStatusSkip)
-	assert.Assert(t, len(rr.Patches) == 0)
+	assert.Equal(t, rr.Status(), engineapi.RuleStatusSkip)
+	assert.Assert(t, len(rr.Patches()) == 0)
 }
 
 func TestProcessPatches_AddAndRemovePathsDontExist_EmptyResult(t *testing.T) {
@@ -151,8 +141,8 @@ func TestProcessPatches_AddAndRemovePathsDontExist_EmptyResult(t *testing.T) {
 		t.Error(err)
 	}
 	rr, _ := applyPatches(rule, *resourceUnstructured)
-	assert.Equal(t, rr.Status, engineapi.RuleStatusPass)
-	assert.Equal(t, len(rr.Patches), 1)
+	assert.Equal(t, rr.Status(), engineapi.RuleStatusPass)
+	assert.Equal(t, len(rr.Patches()), 1)
 }
 
 func TestProcessPatches_AddAndRemovePathsDontExist_ContinueOnError_NotEmptyResult(t *testing.T) {
@@ -166,9 +156,9 @@ func TestProcessPatches_AddAndRemovePathsDontExist_ContinueOnError_NotEmptyResul
 	}
 
 	rr, _ := applyPatches(rule, *resourceUnstructured)
-	assert.Equal(t, rr.Status, engineapi.RuleStatusPass)
-	assert.Assert(t, len(rr.Patches) != 0)
-	assertEqStringAndData(t, `{"path":"/metadata/labels/label3","op":"add","value":"label3Value"}`, rr.Patches[0])
+	assert.Equal(t, rr.Status(), engineapi.RuleStatusPass)
+	assert.Assert(t, len(rr.Patches()) != 0)
+	assertEqStringAndData(t, `{"path":"/metadata/labels/label3","op":"add","value":"label3Value"}`, rr.Patches()[0])
 }
 
 func TestProcessPatches_RemovePathDoesntExist_EmptyResult(t *testing.T) {
@@ -179,8 +169,8 @@ func TestProcessPatches_RemovePathDoesntExist_EmptyResult(t *testing.T) {
 		t.Error(err)
 	}
 	rr, _ := applyPatches(rule, *resourceUnstructured)
-	assert.Equal(t, rr.Status, engineapi.RuleStatusSkip)
-	assert.Assert(t, len(rr.Patches) == 0)
+	assert.Equal(t, rr.Status(), engineapi.RuleStatusSkip)
+	assert.Assert(t, len(rr.Patches()) == 0)
 }
 
 func TestProcessPatches_RemovePathDoesntExist_NotEmptyResult(t *testing.T) {
@@ -192,9 +182,9 @@ func TestProcessPatches_RemovePathDoesntExist_NotEmptyResult(t *testing.T) {
 		t.Error(err)
 	}
 	rr, _ := applyPatches(rule, *resourceUnstructured)
-	assert.Equal(t, rr.Status, engineapi.RuleStatusPass)
-	assert.Assert(t, len(rr.Patches) == 1)
-	assertEqStringAndData(t, `{"path":"/metadata/labels/label2","op":"add","value":"label2Value"}`, rr.Patches[0])
+	assert.Equal(t, rr.Status(), engineapi.RuleStatusPass)
+	assert.Assert(t, len(rr.Patches()) == 1)
+	assertEqStringAndData(t, `{"path":"/metadata/labels/label2","op":"add","value":"label2Value"}`, rr.Patches()[0])
 }
 
 func assertEqStringAndData(t *testing.T, str string, data []byte) {
diff --git a/pkg/engine/mutate/patch/patchJSON6902.go b/pkg/engine/mutate/patch/patchJSON6902.go
index f241a7f4be..ccfa8b4c2f 100644
--- a/pkg/engine/mutate/patch/patchJSON6902.go
+++ b/pkg/engine/mutate/patch/patchJSON6902.go
@@ -12,58 +12,42 @@ import (
 )
 
 // ProcessPatchJSON6902 ...
-func ProcessPatchJSON6902(ruleName string, patchesJSON6902 []byte, resource unstructured.Unstructured, log logr.Logger) (resp engineapi.RuleResponse, patchedResource unstructured.Unstructured) {
+func ProcessPatchJSON6902(ruleName string, patchesJSON6902 []byte, resource unstructured.Unstructured, log logr.Logger) (engineapi.RuleResponse, unstructured.Unstructured) {
 	logger := log.WithValues("rule", ruleName)
 	startTime := time.Now()
 	logger.V(4).Info("started JSON6902 patch", "startTime", startTime)
-	resp.Name = ruleName
-	resp.Type = engineapi.Mutation
 	defer func() {
-		resp.Stats.ProcessingTime = time.Since(startTime)
-		resp.Stats.Timestamp = startTime.Unix()
-		logger.V(4).Info("applied JSON6902 patch", "processingTime", resp.Stats.ProcessingTime.String())
+		logger.V(4).Info("applied JSON6902 patch", "processingTime", time.Since(startTime))
 	}()
-
 	resourceRaw, err := resource.MarshalJSON()
 	if err != nil {
-		resp.Status = engineapi.RuleStatusFail
 		logger.Error(err, "failed to marshal resource")
-		resp.Message = fmt.Sprintf("failed to marshal resource: %v", err)
-		return resp, resource
+		return *engineapi.RuleFail(ruleName, engineapi.Mutation, fmt.Sprintf("failed to marshal resource: %v", err)), resource
 	}
-
 	patchedResourceRaw, err := applyPatchesWithOptions(resourceRaw, patchesJSON6902)
 	if err != nil {
-		resp.Status = engineapi.RuleStatusFail
 		logger.Error(err, "failed to apply JSON Patch")
-		resp.Message = fmt.Sprintf("failed to apply JSON Patch: %v", err)
-		return resp, resource
+		return *engineapi.RuleFail(ruleName, engineapi.Mutation, fmt.Sprintf("failed to apply JSON Patch: %v", err)), resource
 	}
-
 	patchesBytes, err := generatePatches(resourceRaw, patchedResourceRaw)
 	if err != nil {
-		resp.Status = engineapi.RuleStatusFail
 		logger.Error(err, "unable generate patch bytes from base and patched document, apply patchesJSON6902 directly")
-		resp.Message = fmt.Sprintf("unable generate patch bytes from base and patched document, apply patchesJSON6902 directly: %v", err)
-		return resp, resource
+		return *engineapi.RuleFail(
+			ruleName,
+			engineapi.Mutation,
+			fmt.Sprintf("unable generate patch bytes from base and patched document, apply patchesJSON6902 directly: %v", err),
+		), resource
 	}
-
 	for _, p := range patchesBytes {
 		log.V(4).Info("generated JSON Patch (RFC 6902)", "patch", string(p))
 	}
-
+	var patchedResource unstructured.Unstructured
 	err = patchedResource.UnmarshalJSON(patchedResourceRaw)
 	if err != nil {
 		logger.Error(err, "failed to unmarshal resource")
-		resp.Status = engineapi.RuleStatusFail
-		resp.Message = fmt.Sprintf("failed to unmarshal resource: %v", err)
-		return resp, resource
+		return *engineapi.RuleFail(ruleName, engineapi.Mutation, fmt.Sprintf("failed to unmarshal resource: %v", err)), resource
 	}
-
-	resp.Status = engineapi.RuleStatusPass
-	resp.Message = string("applied JSON Patch")
-	resp.Patches = patchesBytes
-	return resp, patchedResource
+	return *engineapi.RulePass(ruleName, engineapi.Mutation, "applied JSON Patch").WithPatches(patchesBytes...), patchedResource
 }
 
 func applyPatchesWithOptions(resource, patch []byte) ([]byte, error) {
diff --git a/pkg/engine/mutate/patch/patchJSON6902_test.go b/pkg/engine/mutate/patch/patchJSON6902_test.go
index 4d755682f1..0fbadc762d 100644
--- a/pkg/engine/mutate/patch/patchJSON6902_test.go
+++ b/pkg/engine/mutate/patch/patchJSON6902_test.go
@@ -51,12 +51,12 @@ func TestTypeConversion(t *testing.T) {
 	assert.Nil(t, err)
 	// apply patches
 	resp, _ := ProcessPatchJSON6902("type-conversion", jsonPatches, resource, logr.Discard())
-	if !assert.Equal(t, engineapi.RuleStatusPass, resp.Status) {
-		t.Fatal(resp.Message)
+	if !assert.Equal(t, engineapi.RuleStatusPass, resp.Status()) {
+		t.Fatal(resp.Message())
 	}
 
-	assert.Equal(t, expectedPatches, resp.Patches,
-		fmt.Sprintf("expectedPatches: %s\ngeneratedPatches: %s", string(expectedPatches[0]), string(resp.Patches[0])))
+	assert.Equal(t, expectedPatches, resp.Patches(),
+		fmt.Sprintf("expectedPatches: %s\ngeneratedPatches: %s", string(expectedPatches[0]), string(resp.Patches()[0])))
 }
 
 func TestJsonPatch(t *testing.T) {
diff --git a/pkg/engine/mutate/patch/patches.go b/pkg/engine/mutate/patch/patches.go
index 53f0ca832e..89b572c6be 100644
--- a/pkg/engine/mutate/patch/patches.go
+++ b/pkg/engine/mutate/patch/patches.go
@@ -50,16 +50,16 @@ func NewPatchesJSON6902(ruleName string, patches string, patchedResource unstruc
 	}
 }
 
-func (h patchesJSON6902Handler) Patch() (resp engineapi.RuleResponse, patchedResource unstructured.Unstructured) {
-	resp.Name = h.ruleName
-	resp.Type = engineapi.Mutation
-
+func (h patchesJSON6902Handler) Patch() (engineapi.RuleResponse, unstructured.Unstructured) {
 	patchesJSON6902, err := ConvertPatchesToJSON(h.patches)
 	if err != nil {
-		resp.Status = engineapi.RuleStatusFail
+		resp := engineapi.RuleFail(
+			h.ruleName,
+			engineapi.Mutation,
+			err.Error(),
+		)
 		h.logger.Error(err, "error in type conversion")
-		resp.Message = err.Error()
-		return resp, unstructured.Unstructured{}
+		return *resp, unstructured.Unstructured{}
 	}
 
 	return ProcessPatchJSON6902(h.ruleName, patchesJSON6902, h.patchedResource, h.logger)
diff --git a/pkg/engine/mutate/patch/strategicMergePatch.go b/pkg/engine/mutate/patch/strategicMergePatch.go
index fe22291e14..a78591910c 100644
--- a/pkg/engine/mutate/patch/strategicMergePatch.go
+++ b/pkg/engine/mutate/patch/strategicMergePatch.go
@@ -15,49 +15,38 @@ import (
 )
 
 // ProcessStrategicMergePatch ...
-func ProcessStrategicMergePatch(ruleName string, overlay interface{}, resource unstructured.Unstructured, log logr.Logger) (resp engineapi.RuleResponse, patchedResource unstructured.Unstructured) {
+func ProcessStrategicMergePatch(ruleName string, overlay interface{}, resource unstructured.Unstructured, log logr.Logger) (engineapi.RuleResponse, unstructured.Unstructured) {
 	startTime := time.Now()
 	logger := log.WithName("ProcessStrategicMergePatch").WithValues("rule", ruleName)
 	logger.V(4).Info("started applying strategicMerge patch", "startTime", startTime)
-	resp.Name = ruleName
-	resp.Type = engineapi.Mutation
 
 	defer func() {
-		resp.Stats.ProcessingTime = time.Since(startTime)
-		resp.Stats.Timestamp = startTime.Unix()
-		logger.V(4).Info("finished applying strategicMerge patch", "processingTime", resp.Stats.ProcessingTime.String())
+		logger.V(4).Info("finished applying strategicMerge patch", "processingTime", time.Since(startTime))
 	}()
 
 	overlayBytes, err := json.Marshal(overlay)
 	if err != nil {
-		resp.Status = engineapi.RuleStatusFail
 		logger.Error(err, "failed to marshal resource")
-		resp.Message = fmt.Sprintf("failed to process patchStrategicMerge: %v", err)
-		return resp, resource
+		return *engineapi.RuleFail(ruleName, engineapi.Mutation, fmt.Sprintf("failed to process patchStrategicMerge: %v", err)), resource
 	}
 
 	base, err := json.Marshal(resource.Object)
 	if err != nil {
-		resp.Status = engineapi.RuleStatusFail
 		logger.Error(err, "failed to marshal resource")
-		resp.Message = fmt.Sprintf("failed to process patchStrategicMerge: %v", err)
-		return resp, resource
+		return *engineapi.RuleFail(ruleName, engineapi.Mutation, fmt.Sprintf("failed to process patchStrategicMerge: %v", err)), resource
 	}
 	patchedBytes, err := strategicMergePatch(logger, string(base), string(overlayBytes))
 	if err != nil {
 		log.Error(err, "failed to apply patchStrategicMerge")
 		msg := fmt.Sprintf("failed to apply patchStrategicMerge: %v", err)
-		resp.Status = engineapi.RuleStatusFail
-		resp.Message = msg
-		return resp, resource
+		return *engineapi.RuleFail(ruleName, engineapi.Mutation, msg), resource
 	}
 
+	var patchedResource unstructured.Unstructured
 	err = patchedResource.UnmarshalJSON(patchedBytes)
 	if err != nil {
 		logger.Error(err, "failed to unmarshal resource")
-		resp.Status = engineapi.RuleStatusFail
-		resp.Message = fmt.Sprintf("failed to process patchStrategicMerge: %v", err)
-		return resp, resource
+		return *engineapi.RuleFail(ruleName, engineapi.Mutation, fmt.Sprintf("failed to process patchStrategicMerge: %v", err)), resource
 	}
 
 	log.V(6).Info("generating JSON patches from patched resource", "patchedResource", patchedResource.Object)
@@ -65,20 +54,15 @@ func ProcessStrategicMergePatch(ruleName string, overlay interface{}, resource u
 	jsonPatches, err := generatePatches(base, patchedBytes)
 	if err != nil {
 		msg := fmt.Sprintf("failed to generated JSON patches from patched resource: %v", err.Error())
-		resp.Status = engineapi.RuleStatusFail
 		log.V(2).Info(msg)
-		resp.Message = msg
-		return resp, patchedResource
+		return *engineapi.RuleFail(ruleName, engineapi.Mutation, msg), patchedResource
 	}
 
 	for _, p := range jsonPatches {
 		log.V(5).Info("generated patch", "patch", string(p))
 	}
 
-	resp.Status = engineapi.RuleStatusPass
-	resp.Patches = jsonPatches
-	resp.Message = "applied strategic merge patch"
-	return resp, patchedResource
+	return *engineapi.RulePass(ruleName, engineapi.Mutation, "applied strategic merge patch").WithPatches(jsonPatches...), patchedResource
 }
 
 func strategicMergePatch(logger logr.Logger, base, overlay string) ([]byte, error) {
diff --git a/pkg/engine/mutation.go b/pkg/engine/mutation.go
index 53ed1770a4..b178e129fd 100644
--- a/pkg/engine/mutation.go
+++ b/pkg/engine/mutation.go
@@ -50,10 +50,9 @@ func (e *engine) mutate(
 			engineapi.Mutation,
 		)
 		matchedResource = resource
+		endTime := time.Now()
 		for _, ruleResp := range ruleResp {
-			ruleResp := ruleResp
-			internal.AddRuleResponse(&resp, &ruleResp, startTime)
-			logger.V(4).Info("finished processing rule", "processingTime", ruleResp.Stats.ProcessingTime.String())
+			resp.Add(startTime, endTime, ruleResp)
 		}
 		if applyRules == kyvernov1.ApplyOne && resp.Stats.RulesAppliedCount > 0 {
 			break
diff --git a/pkg/engine/mutation_test.go b/pkg/engine/mutation_test.go
index 8b49b84be5..ec6e2ca6ab 100644
--- a/pkg/engine/mutation_test.go
+++ b/pkg/engine/mutation_test.go
@@ -124,9 +124,9 @@ func Test_VariableSubstitutionPatchStrategicMerge(t *testing.T) {
 	t.Log(string(expectedPatch))
 
 	assert.Equal(t, len(er.PolicyResponse.Rules), 1)
-	assert.Equal(t, len(er.PolicyResponse.Rules[0].Patches), 1)
-	t.Log(string(er.PolicyResponse.Rules[0].Patches[0]))
-	if !reflect.DeepEqual(expectedPatch, er.PolicyResponse.Rules[0].Patches[0]) {
+	assert.Equal(t, len(er.PolicyResponse.Rules[0].Patches()), 1)
+	t.Log(string(er.PolicyResponse.Rules[0].Patches()[0]))
+	if !reflect.DeepEqual(expectedPatch, er.PolicyResponse.Rules[0].Patches()[0]) {
 		t.Error("patches dont match")
 	}
 }
@@ -195,7 +195,7 @@ func Test_variableSubstitutionPathNotExist(t *testing.T) {
 
 	er := testMutate(context.TODO(), nil, registryclient.NewOrDie(), policyContext, nil)
 	assert.Equal(t, len(er.PolicyResponse.Rules), 1)
-	assert.Assert(t, strings.Contains(er.PolicyResponse.Rules[0].Message, "Unknown key \"name1\" in path"))
+	assert.Assert(t, strings.Contains(er.PolicyResponse.Rules[0].Message(), "Unknown key \"name1\" in path"))
 }
 
 func Test_variableSubstitutionCLI(t *testing.T) {
@@ -291,10 +291,10 @@ func Test_variableSubstitutionCLI(t *testing.T) {
 		),
 	)
 	assert.Equal(t, len(er.PolicyResponse.Rules), 1)
-	assert.Equal(t, len(er.PolicyResponse.Rules[0].Patches), 1)
+	assert.Equal(t, len(er.PolicyResponse.Rules[0].Patches()), 1)
 	t.Log(string(expectedPatch))
-	t.Log(string(er.PolicyResponse.Rules[0].Patches[0]))
-	if !reflect.DeepEqual(expectedPatch, er.PolicyResponse.Rules[0].Patches[0]) {
+	t.Log(string(er.PolicyResponse.Rules[0].Patches()[0]))
+	if !reflect.DeepEqual(expectedPatch, er.PolicyResponse.Rules[0].Patches()[0]) {
 		t.Error("patches don't match")
 	}
 }
@@ -399,11 +399,11 @@ func Test_chained_rules(t *testing.T) {
 	assert.Equal(t, containers[0].(map[string]interface{})["image"], "otherregistry.corp.com/foo/bash:5.0")
 
 	assert.Equal(t, len(er.PolicyResponse.Rules), 2)
-	assert.Equal(t, len(er.PolicyResponse.Rules[0].Patches), 1)
-	assert.Equal(t, len(er.PolicyResponse.Rules[1].Patches), 1)
+	assert.Equal(t, len(er.PolicyResponse.Rules[0].Patches()), 1)
+	assert.Equal(t, len(er.PolicyResponse.Rules[1].Patches()), 1)
 
-	assert.Equal(t, string(er.PolicyResponse.Rules[0].Patches[0]), `{"op":"replace","path":"/spec/containers/0/image","value":"myregistry.corp.com/foo/bash:5.0"}`)
-	assert.Equal(t, string(er.PolicyResponse.Rules[1].Patches[0]), `{"op":"replace","path":"/spec/containers/0/image","value":"otherregistry.corp.com/foo/bash:5.0"}`)
+	assert.Equal(t, string(er.PolicyResponse.Rules[0].Patches()[0]), `{"op":"replace","path":"/spec/containers/0/image","value":"myregistry.corp.com/foo/bash:5.0"}`)
+	assert.Equal(t, string(er.PolicyResponse.Rules[1].Patches()[0]), `{"op":"replace","path":"/spec/containers/0/image","value":"otherregistry.corp.com/foo/bash:5.0"}`)
 }
 
 func Test_precondition(t *testing.T) {
@@ -480,8 +480,8 @@ func Test_precondition(t *testing.T) {
 
 	er := testMutate(context.TODO(), nil, registryclient.NewOrDie(), policyContext, enginetest.ContextLoaderFactory(nil, nil))
 	t.Log(string(expectedPatch))
-	t.Log(string(er.PolicyResponse.Rules[0].Patches[0]))
-	if !reflect.DeepEqual(expectedPatch, er.PolicyResponse.Rules[0].Patches[0]) {
+	t.Log(string(er.PolicyResponse.Rules[0].Patches()[0]))
+	if !reflect.DeepEqual(expectedPatch, er.PolicyResponse.Rules[0].Patches()[0]) {
 		t.Error("patches don't match")
 	}
 }
@@ -574,8 +574,8 @@ func Test_nonZeroIndexNumberPatchesJson6902(t *testing.T) {
 
 	er := testMutate(context.TODO(), nil, registryclient.NewOrDie(), policyContext, enginetest.ContextLoaderFactory(nil, nil))
 	t.Log(string(expectedPatch))
-	t.Log(string(er.PolicyResponse.Rules[0].Patches[0]))
-	if !reflect.DeepEqual(expectedPatch, er.PolicyResponse.Rules[0].Patches[0]) {
+	t.Log(string(er.PolicyResponse.Rules[0].Patches()[0]))
+	if !reflect.DeepEqual(expectedPatch, er.PolicyResponse.Rules[0].Patches()[0]) {
 		t.Error("patches don't match")
 	}
 }
@@ -664,7 +664,7 @@ func Test_foreach(t *testing.T) {
 	er := testMutate(context.TODO(), nil, registryclient.NewOrDie(), policyContext, nil)
 
 	assert.Equal(t, len(er.PolicyResponse.Rules), 1)
-	assert.Equal(t, er.PolicyResponse.Rules[0].Status, engineapi.RuleStatusPass)
+	assert.Equal(t, er.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusPass)
 
 	containers, _, err := unstructured.NestedSlice(er.PatchedResource.Object, "spec", "containers")
 	assert.NilError(t, err)
@@ -766,7 +766,7 @@ func Test_foreach_element_mutation(t *testing.T) {
 	er := testMutate(context.TODO(), nil, registryclient.NewOrDie(), policyContext, nil)
 
 	assert.Equal(t, len(er.PolicyResponse.Rules), 1)
-	assert.Equal(t, er.PolicyResponse.Rules[0].Status, engineapi.RuleStatusPass)
+	assert.Equal(t, er.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusPass)
 
 	containers, _, err := unstructured.NestedSlice(er.PatchedResource.Object, "spec", "containers")
 	assert.NilError(t, err)
@@ -887,7 +887,7 @@ func Test_Container_InitContainer_foreach(t *testing.T) {
 	er := testMutate(context.TODO(), nil, registryclient.NewOrDie(), policyContext, nil)
 
 	assert.Equal(t, len(er.PolicyResponse.Rules), 1)
-	assert.Equal(t, er.PolicyResponse.Rules[0].Status, engineapi.RuleStatusPass)
+	assert.Equal(t, er.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusPass)
 
 	containers, _, err := unstructured.NestedSlice(er.PatchedResource.Object, "spec", "containers")
 	assert.NilError(t, err)
@@ -992,7 +992,7 @@ func Test_foreach_order_mutation_(t *testing.T) {
 	er := testApplyPolicyToResource(t, policyRaw, resourceRaw)
 
 	assert.Equal(t, len(er.PolicyResponse.Rules), 1)
-	assert.Equal(t, er.PolicyResponse.Rules[0].Status, engineapi.RuleStatusPass)
+	assert.Equal(t, er.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusPass)
 
 	containers, _, err := unstructured.NestedSlice(er.PatchedResource.Object, "spec", "containers")
 	assert.NilError(t, err)
@@ -1140,8 +1140,8 @@ func Test_mutate_nested_foreach(t *testing.T) {
 
 	er := testApplyPolicyToResource(t, policyRaw, resourceRaw)
 	assert.Equal(t, len(er.PolicyResponse.Rules), 1)
-	assert.Equal(t, er.PolicyResponse.Rules[0].Status, engineapi.RuleStatusPass)
-	assert.Equal(t, len(er.PolicyResponse.Rules[0].Patches), 2)
+	assert.Equal(t, er.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusPass)
+	assert.Equal(t, len(er.PolicyResponse.Rules[0].Patches()), 2)
 
 	tlsArr, _, err := unstructured.NestedSlice(er.PatchedResource.Object, "spec", "tls")
 	assert.NilError(t, err)
@@ -1580,9 +1580,9 @@ func Test_mutate_existing_resources(t *testing.T) {
 			er := testMutate(context.TODO(), dclient, registryclient.NewOrDie(), policyContext, nil)
 
 			for _, rr := range er.PolicyResponse.Rules {
-				for i, p := range rr.Patches {
-					assert.Equal(t, test.patches[i], string(p), "test %s failed:\nGot %s\nExpected: %s", test.name, rr.Patches[i], test.patches[i])
-					assert.Equal(t, rr.Status, engineapi.RuleStatusPass, rr.Status)
+				for i, p := range rr.Patches() {
+					assert.Equal(t, test.patches[i], string(p), "test %s failed:\nGot %s\nExpected: %s", test.name, rr.Patches()[i], test.patches[i])
+					assert.Equal(t, rr.Status(), engineapi.RuleStatusPass, rr.Status())
 				}
 			}
 		}
@@ -1686,13 +1686,13 @@ func Test_RuleSelectorMutate(t *testing.T) {
 
 	er := testMutate(context.TODO(), nil, registryclient.NewOrDie(), policyContext, nil)
 	assert.Equal(t, len(er.PolicyResponse.Rules), 2)
-	assert.Equal(t, len(er.PolicyResponse.Rules[0].Patches), 1)
-	assert.Equal(t, len(er.PolicyResponse.Rules[1].Patches), 1)
+	assert.Equal(t, len(er.PolicyResponse.Rules[0].Patches()), 1)
+	assert.Equal(t, len(er.PolicyResponse.Rules[1].Patches()), 1)
 
-	if !reflect.DeepEqual(expectedPatch1, er.PolicyResponse.Rules[0].Patches[0]) {
+	if !reflect.DeepEqual(expectedPatch1, er.PolicyResponse.Rules[0].Patches()[0]) {
 		t.Error("rule 1 patches dont match")
 	}
-	if !reflect.DeepEqual(expectedPatch2, er.PolicyResponse.Rules[1].Patches[0]) {
+	if !reflect.DeepEqual(expectedPatch2, er.PolicyResponse.Rules[1].Patches()[0]) {
 		t.Errorf("rule 2 patches dont match")
 	}
 
@@ -1701,9 +1701,9 @@ func Test_RuleSelectorMutate(t *testing.T) {
 
 	er = testMutate(context.TODO(), nil, registryclient.NewOrDie(), policyContext, nil)
 	assert.Equal(t, len(er.PolicyResponse.Rules), 1)
-	assert.Equal(t, len(er.PolicyResponse.Rules[0].Patches), 1)
+	assert.Equal(t, len(er.PolicyResponse.Rules[0].Patches()), 1)
 
-	if !reflect.DeepEqual(expectedPatch1, er.PolicyResponse.Rules[0].Patches[0]) {
+	if !reflect.DeepEqual(expectedPatch1, er.PolicyResponse.Rules[0].Patches()[0]) {
 		t.Error("rule 1 patches dont match")
 	}
 }
diff --git a/pkg/engine/validation.go b/pkg/engine/validation.go
index 5d24afd024..140fbbfef3 100644
--- a/pkg/engine/validation.go
+++ b/pkg/engine/validation.go
@@ -68,10 +68,9 @@ func (e *engine) validate(
 			engineapi.Validation,
 		)
 		matchedResource = resource
+		endTime := time.Now()
 		for _, ruleResp := range ruleResp {
-			ruleResp := ruleResp
-			internal.AddRuleResponse(&resp, &ruleResp, startTime)
-			logger.V(4).Info("finished processing rule", "processingTime", ruleResp.Stats.ProcessingTime.String())
+			resp.Add(startTime, endTime, ruleResp)
 		}
 		if applyRules == kyvernov1.ApplyOne && resp.Stats.RulesAppliedCount > 0 {
 			break
diff --git a/pkg/engine/validation_test.go b/pkg/engine/validation_test.go
index 57593863a4..5f3e18bf57 100644
--- a/pkg/engine/validation_test.go
+++ b/pkg/engine/validation_test.go
@@ -138,7 +138,7 @@ func TestValidate_image_tag_fail(t *testing.T) {
 
 	er := testValidate(context.TODO(), registryclient.NewOrDie(), NewPolicyContextWithJsonContext(kyverno.Create, enginecontext.NewContext()).WithPolicy(&policy).WithNewResource(*resourceUnstructured), cfg, nil)
 	for index, r := range er.PolicyResponse.Rules {
-		assert.Equal(t, r.Message, msgs[index])
+		assert.Equal(t, r.Message(), msgs[index])
 	}
 
 	assert.Assert(t, !er.IsSuccessful())
@@ -238,7 +238,7 @@ func TestValidate_image_tag_pass(t *testing.T) {
 	}
 	er := testValidate(context.TODO(), registryclient.NewOrDie(), NewPolicyContextWithJsonContext(kyverno.Create, enginecontext.NewContext()).WithPolicy(&policy).WithNewResource(*resourceUnstructured), cfg, nil)
 	for index, r := range er.PolicyResponse.Rules {
-		assert.Equal(t, r.Message, msgs[index])
+		assert.Equal(t, r.Message(), msgs[index])
 	}
 	assert.Assert(t, er.IsSuccessful())
 }
@@ -315,7 +315,7 @@ func TestValidate_Fail_anyPattern(t *testing.T) {
 
 	msgs := []string{"validation error: A namespace is required. rule check-default-namespace[0] failed at path /metadata/namespace/ rule check-default-namespace[1] failed at path /metadata/namespace/"}
 	for index, r := range er.PolicyResponse.Rules {
-		assert.Equal(t, r.Message, msgs[index])
+		assert.Equal(t, r.Message(), msgs[index])
 	}
 }
 
@@ -397,7 +397,7 @@ func TestValidate_host_network_port(t *testing.T) {
 	msgs := []string{"validation error: Host network and port are not allowed. rule validate-host-network-port failed at path /spec/containers/0/ports/0/hostPort/"}
 
 	for index, r := range er.PolicyResponse.Rules {
-		assert.Equal(t, r.Message, msgs[index])
+		assert.Equal(t, r.Message(), msgs[index])
 	}
 	assert.Assert(t, !er.IsSuccessful())
 }
@@ -487,7 +487,7 @@ func TestValidate_anchor_arraymap_pass(t *testing.T) {
 	msgs := []string{"validation rule 'validate-host-path' passed."}
 
 	for index, r := range er.PolicyResponse.Rules {
-		assert.Equal(t, r.Message, msgs[index])
+		assert.Equal(t, r.Message(), msgs[index])
 	}
 	assert.Assert(t, er.IsSuccessful())
 }
@@ -575,7 +575,7 @@ func TestValidate_anchor_arraymap_fail(t *testing.T) {
 	msgs := []string{"validation error: Host path '/var/lib/' is not allowed. rule validate-host-path failed at path /spec/volumes/0/hostPath/path/"}
 
 	for index, r := range er.PolicyResponse.Rules {
-		assert.Equal(t, r.Message, msgs[index])
+		assert.Equal(t, r.Message(), msgs[index])
 	}
 	assert.Assert(t, !er.IsSuccessful())
 }
@@ -645,7 +645,7 @@ func TestValidate_anchor_map_notfound(t *testing.T) {
 	msgs := []string{"validation rule 'pod rule 2' passed."}
 
 	for index, r := range er.PolicyResponse.Rules {
-		assert.Equal(t, r.Message, msgs[index])
+		assert.Equal(t, r.Message(), msgs[index])
 	}
 	assert.Assert(t, er.IsSuccessful())
 }
@@ -718,7 +718,7 @@ func TestValidate_anchor_map_found_valid(t *testing.T) {
 	msgs := []string{"validation rule 'pod rule 2' passed."}
 
 	for index, r := range er.PolicyResponse.Rules {
-		assert.Equal(t, r.Message, msgs[index])
+		assert.Equal(t, r.Message(), msgs[index])
 	}
 
 	assert.Assert(t, er.IsSuccessful())
@@ -792,7 +792,7 @@ func TestValidate_inequality_List_Processing(t *testing.T) {
 	msgs := []string{"validation rule 'pod rule 2' passed."}
 
 	for index, r := range er.PolicyResponse.Rules {
-		assert.Equal(t, r.Message, msgs[index])
+		assert.Equal(t, r.Message(), msgs[index])
 	}
 
 	assert.Assert(t, er.IsSuccessful())
@@ -872,7 +872,7 @@ func TestValidate_inequality_List_ProcessingBrackets(t *testing.T) {
 	msgs := []string{"validation rule 'pod rule 2' passed."}
 
 	for index, r := range er.PolicyResponse.Rules {
-		assert.Equal(t, r.Message, msgs[index])
+		assert.Equal(t, r.Message(), msgs[index])
 	}
 
 	assert.Assert(t, er.IsSuccessful())
@@ -946,7 +946,7 @@ func TestValidate_anchor_map_found_invalid(t *testing.T) {
 	msgs := []string{"validation error: pod: validate run as non root user. rule pod rule 2 failed at path /spec/securityContext/runAsNonRoot/"}
 
 	for index, r := range er.PolicyResponse.Rules {
-		assert.Equal(t, r.Message, msgs[index])
+		assert.Equal(t, r.Message(), msgs[index])
 	}
 	assert.Assert(t, !er.IsSuccessful())
 }
@@ -1021,8 +1021,8 @@ func TestValidate_AnchorList_pass(t *testing.T) {
 	msgs := []string{"validation rule 'pod image rule' passed."}
 
 	for index, r := range er.PolicyResponse.Rules {
-		t.Log(r.Message)
-		assert.Equal(t, r.Message, msgs[index])
+		t.Log(r.Message())
+		assert.Equal(t, r.Message(), msgs[index])
 	}
 	assert.Assert(t, er.IsSuccessful())
 }
@@ -1236,7 +1236,7 @@ func TestValidate_existenceAnchor_pass(t *testing.T) {
 	msgs := []string{"validation rule 'pod image rule' passed."}
 
 	for index, r := range er.PolicyResponse.Rules {
-		assert.Equal(t, r.Message, msgs[index])
+		assert.Equal(t, r.Message(), msgs[index])
 	}
 	assert.Assert(t, er.IsSuccessful())
 }
@@ -1324,7 +1324,7 @@ func TestValidate_negationAnchor_deny(t *testing.T) {
 	msgs := []string{"validation error: Host path is not allowed. rule validate-host-path failed at path /spec/volumes/0/hostPath/"}
 
 	for index, r := range er.PolicyResponse.Rules {
-		assert.Equal(t, r.Message, msgs[index])
+		assert.Equal(t, r.Message(), msgs[index])
 	}
 	assert.Assert(t, !er.IsSuccessful())
 }
@@ -1411,7 +1411,7 @@ func TestValidate_negationAnchor_pass(t *testing.T) {
 	msgs := []string{"validation rule 'validate-host-path' passed."}
 
 	for index, r := range er.PolicyResponse.Rules {
-		assert.Equal(t, r.Message, msgs[index])
+		assert.Equal(t, r.Message(), msgs[index])
 	}
 	assert.Assert(t, er.IsSuccessful())
 }
@@ -1483,8 +1483,8 @@ func Test_VariableSubstitutionPathNotExistInPattern(t *testing.T) {
 	er := testValidate(context.TODO(), registryclient.NewOrDie(), policyContext, cfg, nil)
 
 	assert.Equal(t, len(er.PolicyResponse.Rules), 1)
-	assert.Equal(t, er.PolicyResponse.Rules[0].Status, engineapi.RuleStatusError)
-	assert.Assert(t, strings.Contains(er.PolicyResponse.Rules[0].Message, "Unknown key \"name1\" in path"))
+	assert.Equal(t, er.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusError)
+	assert.Assert(t, strings.Contains(er.PolicyResponse.Rules[0].Message(), "Unknown key \"name1\" in path"))
 }
 
 func Test_VariableSubstitutionPathNotExistInAnyPattern_OnePatternStatisfiesButSubstitutionFails(t *testing.T) {
@@ -1573,8 +1573,8 @@ func Test_VariableSubstitutionPathNotExistInAnyPattern_OnePatternStatisfiesButSu
 	er := testValidate(context.TODO(), registryclient.NewOrDie(), policyContext, cfg, nil)
 
 	assert.Equal(t, len(er.PolicyResponse.Rules), 1)
-	assert.Equal(t, er.PolicyResponse.Rules[0].Status, engineapi.RuleStatusError)
-	assert.Assert(t, strings.Contains(er.PolicyResponse.Rules[0].Message, "Unknown key \"name1\" in path"))
+	assert.Equal(t, er.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusError)
+	assert.Assert(t, strings.Contains(er.PolicyResponse.Rules[0].Message(), "Unknown key \"name1\" in path"))
 }
 
 func Test_VariableSubstitution_NotOperatorWithStringVariable(t *testing.T) {
@@ -1629,8 +1629,8 @@ func Test_VariableSubstitution_NotOperatorWithStringVariable(t *testing.T) {
 
 	policyContext := NewPolicyContextWithJsonContext(kyverno.Create, ctx).WithPolicy(&policy).WithNewResource(*resourceUnstructured)
 	er := testValidate(context.TODO(), registryclient.NewOrDie(), policyContext, cfg, nil)
-	assert.Equal(t, er.PolicyResponse.Rules[0].Status, engineapi.RuleStatusFail)
-	assert.Equal(t, er.PolicyResponse.Rules[0].Message, "validation error: rule not-operator-with-variable-should-alway-fail-validation failed at path /spec/content/")
+	assert.Equal(t, er.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusFail)
+	assert.Equal(t, er.PolicyResponse.Rules[0].Message(), "validation error: rule not-operator-with-variable-should-alway-fail-validation failed at path /spec/content/")
 }
 
 func Test_VariableSubstitutionPathNotExistInAnyPattern_AllPathNotPresent(t *testing.T) {
@@ -1719,8 +1719,8 @@ func Test_VariableSubstitutionPathNotExistInAnyPattern_AllPathNotPresent(t *test
 	er := testValidate(context.TODO(), registryclient.NewOrDie(), policyContext, cfg, nil)
 
 	assert.Equal(t, len(er.PolicyResponse.Rules), 1)
-	assert.Equal(t, er.PolicyResponse.Rules[0].Status, engineapi.RuleStatusError)
-	assert.Assert(t, strings.Contains(er.PolicyResponse.Rules[0].Message, "Unknown key \"name1\" in path"))
+	assert.Equal(t, er.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusError)
+	assert.Assert(t, strings.Contains(er.PolicyResponse.Rules[0].Message(), "Unknown key \"name1\" in path"))
 }
 
 func Test_VariableSubstitutionPathNotExistInAnyPattern_AllPathPresent_NonePatternSatisfy(t *testing.T) {
@@ -1808,8 +1808,8 @@ func Test_VariableSubstitutionPathNotExistInAnyPattern_AllPathPresent_NonePatter
 	policyContext := NewPolicyContextWithJsonContext(kyverno.Create, ctx).WithPolicy(&policy).WithNewResource(*resourceUnstructured)
 	er := testValidate(context.TODO(), registryclient.NewOrDie(), policyContext, cfg, nil)
 
-	assert.Equal(t, er.PolicyResponse.Rules[0].Status, engineapi.RuleStatusFail)
-	assert.Equal(t, er.PolicyResponse.Rules[0].Message,
+	assert.Equal(t, er.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusFail)
+	assert.Equal(t, er.PolicyResponse.Rules[0].Message(),
 		"validation error: rule test-path-not-exist[0] failed at path /spec/template/spec/containers/0/name/ rule test-path-not-exist[1] failed at path /spec/template/spec/containers/0/name/")
 }
 
@@ -1909,8 +1909,8 @@ func Test_VariableSubstitutionValidate_VariablesInMessageAreResolved(t *testing.
 
 	policyContext := NewPolicyContextWithJsonContext(kyverno.Create, ctx).WithPolicy(&policy).WithNewResource(*resourceUnstructured)
 	er := testValidate(context.TODO(), registryclient.NewOrDie(), policyContext, cfg, nil)
-	assert.Equal(t, er.PolicyResponse.Rules[0].Status, engineapi.RuleStatusFail)
-	assert.Equal(t, er.PolicyResponse.Rules[0].Message, "The animal cow is not in the allowed list of animals.")
+	assert.Equal(t, er.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusFail)
+	assert.Equal(t, er.PolicyResponse.Rules[0].Message(), "The animal cow is not in the allowed list of animals.")
 }
 
 func Test_Flux_Kustomization_PathNotPresent(t *testing.T) {
@@ -1961,8 +1961,8 @@ func Test_Flux_Kustomization_PathNotPresent(t *testing.T) {
 		er := testValidate(context.TODO(), registryclient.NewOrDie(), policyContext, cfg, nil)
 
 		for i, rule := range er.PolicyResponse.Rules {
-			assert.Equal(t, er.PolicyResponse.Rules[i].Status, test.expectedResults[i], "\ntest %s failed\nexpected: %s\nactual: %s", test.name, test.expectedResults[i], er.PolicyResponse.Rules[i].Status)
-			assert.Equal(t, er.PolicyResponse.Rules[i].Message, test.expectedMessages[i], "\ntest %s failed\nexpected: %s\nactual: %s", test.name, test.expectedMessages[i], rule.Message)
+			assert.Equal(t, er.PolicyResponse.Rules[i].Status(), test.expectedResults[i], "\ntest %s failed\nexpected: %s\nactual: %s", test.name, test.expectedResults[i], er.PolicyResponse.Rules[i].Status())
+			assert.Equal(t, er.PolicyResponse.Rules[i].Message(), test.expectedMessages[i], "\ntest %s failed\nexpected: %s\nactual: %s", test.name, test.expectedMessages[i], rule.Message())
 		}
 	}
 }
@@ -2223,7 +2223,7 @@ func TestValidate_context_variable_substitution_CLI(t *testing.T) {
 		),
 	)
 	for index, r := range er.PolicyResponse.Rules {
-		assert.Equal(t, r.Message, msgs[index])
+		assert.Equal(t, r.Message(), msgs[index])
 	}
 	assert.Assert(t, !er.IsSuccessful())
 }
@@ -3088,9 +3088,9 @@ func testForEach(t *testing.T, policyraw []byte, resourceRaw []byte, msg string,
 		WithNewResource(*resourceUnstructured)
 	er := testValidate(context.TODO(), registryclient.NewOrDie(), policyContext, cfg, contextLoader)
 
-	assert.Equal(t, er.PolicyResponse.Rules[0].Status, status)
+	assert.Equal(t, er.PolicyResponse.Rules[0].Status(), status)
 	if msg != "" {
-		assert.Equal(t, er.PolicyResponse.Rules[0].Message, msg)
+		assert.Equal(t, er.PolicyResponse.Rules[0].Message(), msg)
 	}
 }
 
@@ -3151,7 +3151,7 @@ func Test_delete_ignore_pattern(t *testing.T) {
 
 	engineResponseCreate := testValidate(context.TODO(), registryclient.NewOrDie(), policyContextCreate, cfg, nil)
 	assert.Equal(t, len(engineResponseCreate.PolicyResponse.Rules), 1)
-	assert.Equal(t, engineResponseCreate.PolicyResponse.Rules[0].Status, engineapi.RuleStatusFail)
+	assert.Equal(t, engineResponseCreate.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusFail)
 
 	policyContextDelete := NewPolicyContextWithJsonContext(kyverno.Create, ctx).
 		WithPolicy(&policy).
diff --git a/pkg/event/events.go b/pkg/event/events.go
index ee128c7ce5..4950e9bef2 100644
--- a/pkg/event/events.go
+++ b/pkg/event/events.go
@@ -28,13 +28,13 @@ func buildPolicyEventMessage(resp *engineapi.RuleResponse, resource engineapi.Re
 		fmt.Fprintf(&b, "%s %s", resource.Kind, resource.Name)
 	}
 
-	fmt.Fprintf(&b, ": [%s] %s", resp.Name, resp.Status)
+	fmt.Fprintf(&b, ": [%s] %s", resp.Name(), resp.Status())
 	if blocked {
 		fmt.Fprintf(&b, " (blocked)")
 	}
 
-	if resp.Message != "" {
-		fmt.Fprintf(&b, "; %s", resp.Message)
+	if resp.Message() != "" {
+		fmt.Fprintf(&b, "; %s", resp.Message())
 	}
 
 	return b.String()
@@ -73,7 +73,7 @@ func NewResourceViolationEvent(source Source, reason Reason, engineResponse engi
 	defer bldr.Reset()
 
 	fmt.Fprintf(&bldr, "policy %s/%s %s: %s", engineResponse.Policy.GetName(),
-		ruleResp.Name, ruleResp.Status, ruleResp.Message)
+		ruleResp.Name(), ruleResp.Status(), ruleResp.Message())
 	resource := engineResponse.GetResourceSpec()
 
 	return Info{
@@ -124,13 +124,14 @@ func NewBackgroundSuccessEvent(policy, rule string, source Source, r *unstructur
 }
 
 func NewPolicyExceptionEvents(engineResponse engineapi.EngineResponse, ruleResp *engineapi.RuleResponse, source Source) []Info {
-	exceptionName, exceptionNamespace := ruleResp.Exception.GetName(), ruleResp.Exception.GetNamespace()
-	policyMessage := fmt.Sprintf("resource %s was skipped from rule %s due to policy exception %s/%s", resourceKey(engineResponse.PatchedResource), ruleResp.Name, exceptionNamespace, exceptionName)
+	exception := ruleResp.Exception()
+	exceptionName, exceptionNamespace := exception.GetName(), exception.GetNamespace()
+	policyMessage := fmt.Sprintf("resource %s was skipped from rule %s due to policy exception %s/%s", resourceKey(engineResponse.PatchedResource), ruleResp.Name(), exceptionNamespace, exceptionName)
 	var exceptionMessage string
 	if engineResponse.Policy.GetNamespace() == "" {
-		exceptionMessage = fmt.Sprintf("resource %s was skipped from policy rule %s/%s", resourceKey(engineResponse.PatchedResource), engineResponse.Policy.GetName(), ruleResp.Name)
+		exceptionMessage = fmt.Sprintf("resource %s was skipped from policy rule %s/%s", resourceKey(engineResponse.PatchedResource), engineResponse.Policy.GetName(), ruleResp.Name())
 	} else {
-		exceptionMessage = fmt.Sprintf("resource %s was skipped from policy rule %s/%s/%s", resourceKey(engineResponse.PatchedResource), engineResponse.Policy.GetNamespace(), engineResponse.Policy.GetName(), ruleResp.Name)
+		exceptionMessage = fmt.Sprintf("resource %s was skipped from policy rule %s/%s/%s", resourceKey(engineResponse.PatchedResource), engineResponse.Policy.GetNamespace(), engineResponse.Policy.GetName(), ruleResp.Name())
 	}
 	policyEvent := Info{
 		Kind:      getPolicyKind(engineResponse.Policy),
diff --git a/pkg/metrics/parsers.go b/pkg/metrics/parsers.go
index 35a0092a5d..09d092ce21 100644
--- a/pkg/metrics/parsers.go
+++ b/pkg/metrics/parsers.go
@@ -54,7 +54,7 @@ func ParseResourceRequestOperation(requestOperationStr string) (ResourceRequestO
 }
 
 func ParseRuleTypeFromEngineRuleResponse(rule engineapi.RuleResponse) RuleType {
-	switch rule.Type {
+	switch rule.RuleType() {
 	case "Validation":
 		return Validate
 	case "Mutation":
diff --git a/pkg/policy/policy_controller.go b/pkg/policy/policy_controller.go
index 3c88c71779..9b7ba9c765 100644
--- a/pkg/policy/policy_controller.go
+++ b/pkg/policy/policy_controller.go
@@ -416,8 +416,8 @@ func (pc *PolicyController) handleUpdateRequest(ur *kyvernov1beta1.UpdateRequest
 	}
 
 	for _, ruleResponse := range engineResponse.PolicyResponse.Rules {
-		if ruleResponse.Status != engineapi.RuleStatusPass {
-			pc.log.Error(err, "can not create new UR on policy update", "policy", policy.GetName(), "rule", rule.Name, "rule.Status", ruleResponse.Status)
+		if ruleResponse.Status() != engineapi.RuleStatusPass {
+			pc.log.Error(err, "can not create new UR on policy update", "policy", policy.GetName(), "rule", rule.Name, "rule.Status", ruleResponse.Status())
 			continue
 		}
 
diff --git a/pkg/utils/annotations.go b/pkg/utils/annotations.go
index 6c9d0fb64b..f4698c9748 100644
--- a/pkg/utils/annotations.go
+++ b/pkg/utils/annotations.go
@@ -122,14 +122,14 @@ func annotationFromEngineResponses(engineResponses []engineapi.EngineResponse, l
 func annotationFromPolicyResponse(policyResponse engineapi.PolicyResponse, log logr.Logger) []RulePatch {
 	var RulePatches []RulePatch
 	for _, ruleInfo := range policyResponse.Rules {
-		for _, patch := range ruleInfo.Patches {
+		for _, patch := range ruleInfo.Patches() {
 			var patchmap map[string]interface{}
 			if err := json.Unmarshal(patch, &patchmap); err != nil {
 				log.Error(err, "Failed to parse JSON patch bytes")
 				continue
 			}
 			rp := RulePatch{
-				RuleName: ruleInfo.Name,
+				RuleName: ruleInfo.Name(),
 				Op:       patchmap["op"].(string),
 				Path:     patchmap["path"].(string),
 			}
diff --git a/pkg/utils/annotations_test.go b/pkg/utils/annotations_test.go
index 0e64b67611..b4b7af8318 100644
--- a/pkg/utils/annotations_test.go
+++ b/pkg/utils/annotations_test.go
@@ -20,11 +20,7 @@ func newPolicyResponse(rule string, patchesStr []string, status engineapi.RuleSt
 
 	return engineapi.PolicyResponse{
 		Rules: []engineapi.RuleResponse{
-			{
-				Name:    rule,
-				Patches: patches,
-				Status:  status,
-			},
+			*engineapi.NewRuleResponse(rule, engineapi.Mutation, "", status).WithPatches(patches...),
 		},
 	}
 }
diff --git a/pkg/utils/report/results.go b/pkg/utils/report/results.go
index 83cec00177..f6185d1071 100644
--- a/pkg/utils/report/results.go
+++ b/pkg/utils/report/results.go
@@ -89,9 +89,9 @@ func EngineResponseToReportResults(response engineapi.EngineResponse) []policyre
 		result := policyreportv1alpha2.PolicyReportResult{
 			Source:  kyvernov1.ValueKyvernoApp,
 			Policy:  key,
-			Rule:    ruleResult.Name,
-			Message: ruleResult.Message,
-			Result:  toPolicyResult(ruleResult.Status),
+			Rule:    ruleResult.Name(),
+			Message: ruleResult.Message(),
+			Result:  toPolicyResult(ruleResult.Status()),
 			Scored:  annotations[kyvernov1.AnnotationPolicyScored] != "false",
 			Timestamp: metav1.Timestamp{
 				Seconds: time.Now().Unix(),
@@ -99,9 +99,10 @@ func EngineResponseToReportResults(response engineapi.EngineResponse) []policyre
 			Category: annotations[kyvernov1.AnnotationPolicyCategory],
 			Severity: severityFromString(annotations[kyvernov1.AnnotationPolicySeverity]),
 		}
-		if ruleResult.PodSecurityChecks != nil {
+		pss := ruleResult.PodSecurityChecks()
+		if pss != nil {
 			var controls []string
-			for _, check := range ruleResult.PodSecurityChecks.Checks {
+			for _, check := range pss.Checks {
 				if !check.CheckResult.Allowed {
 					controls = append(controls, check.ID)
 				}
@@ -109,8 +110,8 @@ func EngineResponseToReportResults(response engineapi.EngineResponse) []policyre
 			if len(controls) > 0 {
 				sort.Strings(controls)
 				result.Properties = map[string]string{
-					"standard": string(ruleResult.PodSecurityChecks.Level),
-					"version":  ruleResult.PodSecurityChecks.Version,
+					"standard": string(pss.Level),
+					"version":  pss.Version,
 					"controls": strings.Join(controls, ","),
 				}
 			}
diff --git a/pkg/webhooks/resource/generation/handler.go b/pkg/webhooks/resource/generation/handler.go
index 1751574916..6fcfd79213 100644
--- a/pkg/webhooks/resource/generation/handler.go
+++ b/pkg/webhooks/resource/generation/handler.go
@@ -90,7 +90,7 @@ func getAppliedRules(policy kyvernov1.PolicyInterface, applied []engineapi.RuleR
 			continue
 		}
 		for _, applied := range applied {
-			if applied.Name == rule.Name && applied.Type == engineapi.Generation {
+			if applied.Name() == rule.Name && applied.RuleType() == engineapi.Generation {
 				rules = append(rules, rule)
 			}
 		}
@@ -113,9 +113,9 @@ func (h *generationHandler) handleTrigger(
 		}
 		engineResponse := h.engine.ApplyBackgroundChecks(ctx, policyContext)
 		for _, rule := range engineResponse.PolicyResponse.Rules {
-			if rule.Status == engineapi.RuleStatusPass {
+			if rule.Status() == engineapi.RuleStatusPass {
 				appliedRules = append(appliedRules, rule)
-			} else if rule.Status == engineapi.RuleStatusFail {
+			} else if rule.Status() == engineapi.RuleStatusFail {
 				failedRules = append(failedRules, rule)
 			}
 		}
diff --git a/pkg/webhooks/resource/updaterequest.go b/pkg/webhooks/resource/updaterequest.go
index 7f536906dc..b49a8a491e 100644
--- a/pkg/webhooks/resource/updaterequest.go
+++ b/pkg/webhooks/resource/updaterequest.go
@@ -50,7 +50,7 @@ func (h *resourceHandlers) handleMutateExisting(ctx context.Context, logger logr
 		engineResponse := h.engine.ApplyBackgroundChecks(ctx, policyContext)
 
 		for _, rule := range engineResponse.PolicyResponse.Rules {
-			if rule.Status == engineapi.RuleStatusPass {
+			if rule.Status() == engineapi.RuleStatusPass {
 				rules = append(rules, rule)
 			}
 		}
diff --git a/pkg/webhooks/resource/validation_test.go b/pkg/webhooks/resource/validation_test.go
index d8208b7ab4..bbf2830556 100644
--- a/pkg/webhooks/resource/validation_test.go
+++ b/pkg/webhooks/resource/validation_test.go
@@ -1072,8 +1072,8 @@ func TestValidate_failure_action_overrides(t *testing.T) {
 			)
 			if tc.blocked && tc.messages != nil {
 				for _, r := range er.PolicyResponse.Rules {
-					msg := tc.messages[r.Name]
-					assert.Equal(t, r.Message, msg)
+					msg := tc.messages[r.Name()]
+					assert.Equal(t, r.Message(), msg)
 				}
 			}
 
diff --git a/pkg/webhooks/utils/block.go b/pkg/webhooks/utils/block.go
index ed0c724712..bebe764933 100644
--- a/pkg/webhooks/utils/block.go
+++ b/pkg/webhooks/utils/block.go
@@ -44,9 +44,9 @@ func GetBlockedMessages(engineResponses []engineapi.EngineResponse) string {
 	for _, er := range engineResponses {
 		ruleToReason := make(map[string]string)
 		for _, rule := range er.PolicyResponse.Rules {
-			if rule.Status != engineapi.RuleStatusPass {
-				ruleToReason[rule.Name] = rule.Message
-				if rule.Status == engineapi.RuleStatusFail {
+			if rule.Status() != engineapi.RuleStatusPass {
+				ruleToReason[rule.Name()] = rule.Message()
+				if rule.Status() == engineapi.RuleStatusFail {
 					hasViolations = true
 				}
 			}
diff --git a/pkg/webhooks/utils/block_test.go b/pkg/webhooks/utils/block_test.go
index 6a272c0d14..5830a58282 100644
--- a/pkg/webhooks/utils/block_test.go
+++ b/pkg/webhooks/utils/block_test.go
@@ -87,11 +87,7 @@ func TestBlockRequest(t *testing.T) {
 			engineResponses: []engineapi.EngineResponse{
 				engineapi.NewEngineResponse(resource, enforcePolicy, nil, &engineapi.PolicyResponse{
 					Rules: []engineapi.RuleResponse{
-						{
-							Name:    "rule-fail",
-							Status:  engineapi.RuleStatusFail,
-							Message: "message fail",
-						},
+						*engineapi.RuleFail("rule-fail", engineapi.Validation, "message fail"),
 					},
 				}, time.Now()),
 			},
@@ -105,11 +101,7 @@ func TestBlockRequest(t *testing.T) {
 			engineResponses: []engineapi.EngineResponse{
 				engineapi.NewEngineResponse(resource, auditPolicy, nil, &engineapi.PolicyResponse{
 					Rules: []engineapi.RuleResponse{
-						{
-							Name:    "rule-fail",
-							Status:  engineapi.RuleStatusFail,
-							Message: "message fail",
-						},
+						*engineapi.RuleFail("rule-fail", engineapi.Validation, "message fail"),
 					},
 				}, time.Now()),
 			},
@@ -123,11 +115,7 @@ func TestBlockRequest(t *testing.T) {
 			engineResponses: []engineapi.EngineResponse{
 				engineapi.NewEngineResponse(resource, auditPolicy, nil, &engineapi.PolicyResponse{
 					Rules: []engineapi.RuleResponse{
-						{
-							Name:    "rule-error",
-							Status:  engineapi.RuleStatusError,
-							Message: "message error",
-						},
+						*engineapi.RuleError("rule-error", engineapi.Validation, "message error", nil),
 					},
 				}, time.Now()),
 			},
@@ -141,11 +129,7 @@ func TestBlockRequest(t *testing.T) {
 			engineResponses: []engineapi.EngineResponse{
 				engineapi.NewEngineResponse(resource, auditPolicy, nil, &engineapi.PolicyResponse{
 					Rules: []engineapi.RuleResponse{
-						{
-							Name:    "rule-error",
-							Status:  engineapi.RuleStatusError,
-							Message: "message error",
-						},
+						*engineapi.RuleError("rule-error", engineapi.Validation, "message error", nil),
 					},
 				}, time.Now()),
 			},
@@ -159,11 +143,7 @@ func TestBlockRequest(t *testing.T) {
 			engineResponses: []engineapi.EngineResponse{
 				engineapi.NewEngineResponse(resource, auditPolicy, nil, &engineapi.PolicyResponse{
 					Rules: []engineapi.RuleResponse{
-						{
-							Name:    "rule-warning",
-							Status:  engineapi.RuleStatusWarn,
-							Message: "message warning",
-						},
+						*engineapi.NewRuleResponse("rule-warning", engineapi.Validation, "message warning", engineapi.RuleStatusWarn),
 					},
 				}, time.Now()),
 			},
@@ -177,11 +157,7 @@ func TestBlockRequest(t *testing.T) {
 			engineResponses: []engineapi.EngineResponse{
 				engineapi.NewEngineResponse(resource, auditPolicy, nil, &engineapi.PolicyResponse{
 					Rules: []engineapi.RuleResponse{
-						{
-							Name:    "rule-warning",
-							Status:  engineapi.RuleStatusWarn,
-							Message: "message warning",
-						},
+						*engineapi.NewRuleResponse("rule-warning", engineapi.Validation, "message warning", engineapi.RuleStatusWarn),
 					},
 				}, time.Now()),
 			},
@@ -229,11 +205,7 @@ func TestGetBlockedMessages(t *testing.T) {
 			engineResponses: []engineapi.EngineResponse{
 				engineapi.NewEngineResponse(resource, enforcePolicy, nil, &engineapi.PolicyResponse{
 					Rules: []engineapi.RuleResponse{
-						{
-							Name:    "rule-fail",
-							Status:  engineapi.RuleStatusFail,
-							Message: "message fail",
-						},
+						*engineapi.RuleFail("rule-fail", engineapi.Validation, "message fail"),
 					},
 				}, time.Now()),
 			},
@@ -245,11 +217,7 @@ func TestGetBlockedMessages(t *testing.T) {
 			engineResponses: []engineapi.EngineResponse{
 				engineapi.NewEngineResponse(resource, enforcePolicy, nil, &engineapi.PolicyResponse{
 					Rules: []engineapi.RuleResponse{
-						{
-							Name:    "rule-error",
-							Status:  engineapi.RuleStatusError,
-							Message: "message error",
-						},
+						*engineapi.RuleError("rule-error", engineapi.Validation, "message error", nil),
 					},
 				}, time.Now()),
 			},
@@ -261,16 +229,8 @@ func TestGetBlockedMessages(t *testing.T) {
 			engineResponses: []engineapi.EngineResponse{
 				engineapi.NewEngineResponse(resource, enforcePolicy, nil, &engineapi.PolicyResponse{
 					Rules: []engineapi.RuleResponse{
-						{
-							Name:    "rule-fail",
-							Status:  engineapi.RuleStatusFail,
-							Message: "message fail",
-						},
-						{
-							Name:    "rule-error",
-							Status:  engineapi.RuleStatusError,
-							Message: "message error",
-						},
+						*engineapi.RuleFail("rule-fail", engineapi.Validation, "message fail"),
+						*engineapi.RuleError("rule-error", engineapi.Validation, "message error", nil),
 					},
 				}, time.Now()),
 			},
diff --git a/pkg/webhooks/utils/error.go b/pkg/webhooks/utils/error.go
index 4c4f6b6cb4..b54be16193 100644
--- a/pkg/webhooks/utils/error.go
+++ b/pkg/webhooks/utils/error.go
@@ -16,7 +16,7 @@ func GetErrorMsg(engineReponses []engineapi.EngineResponse) string {
 			resourceInfo = fmt.Sprintf("%s/%s/%s", er.Resource.GetKind(), er.Resource.GetNamespace(), er.Resource.GetName())
 			str = append(str, fmt.Sprintf("failed policy %s:", er.Policy.GetName()))
 			for _, rule := range er.PolicyResponse.Rules {
-				if rule.Status != engineapi.RuleStatusPass {
+				if rule.Status() != engineapi.RuleStatusPass {
 					str = append(str, rule.String())
 				}
 			}
diff --git a/pkg/webhooks/utils/event.go b/pkg/webhooks/utils/event.go
index f7395af6c4..d3678d4828 100644
--- a/pkg/webhooks/utils/event.go
+++ b/pkg/webhooks/utils/event.go
@@ -21,7 +21,7 @@ func GenerateEvents(engineResponses []engineapi.EngineResponse, blocked bool) []
 		}
 		if !er.IsSuccessful() {
 			for i, ruleResp := range er.PolicyResponse.Rules {
-				if ruleResp.Status == engineapi.RuleStatusFail || ruleResp.Status == engineapi.RuleStatusError {
+				if ruleResp.Status() == engineapi.RuleStatusFail || ruleResp.Status() == engineapi.RuleStatusError {
 					e := event.NewPolicyFailEvent(event.AdmissionController, event.PolicyViolation, er, &er.PolicyResponse.Rules[i], blocked)
 					events = append(events, e)
 				}
@@ -32,8 +32,7 @@ func GenerateEvents(engineResponses []engineapi.EngineResponse, blocked bool) []
 			}
 		} else if er.IsSkipped() { // Handle PolicyException Event
 			for i, ruleResp := range er.PolicyResponse.Rules {
-				isException := ruleResp.Exception != nil
-				if ruleResp.Status == engineapi.RuleStatusSkip && !blocked && isException {
+				if ruleResp.Status() == engineapi.RuleStatusSkip && !blocked && ruleResp.IsException() {
 					events = append(events, event.NewPolicyExceptionEvents(er, &er.PolicyResponse.Rules[i], event.AdmissionController)...)
 				}
 			}
diff --git a/pkg/webhooks/utils/warning.go b/pkg/webhooks/utils/warning.go
index 5390b871ce..9b9b15fae7 100644
--- a/pkg/webhooks/utils/warning.go
+++ b/pkg/webhooks/utils/warning.go
@@ -10,8 +10,8 @@ func GetWarningMessages(engineResponses []engineapi.EngineResponse) []string {
 	var warnings []string
 	for _, er := range engineResponses {
 		for _, rule := range er.PolicyResponse.Rules {
-			if rule.Status != engineapi.RuleStatusPass && rule.Status != engineapi.RuleStatusSkip {
-				msg := fmt.Sprintf("policy %s.%s: %s", er.Policy.GetName(), rule.Name, rule.Message)
+			if rule.Status() != engineapi.RuleStatusPass && rule.Status() != engineapi.RuleStatusSkip {
+				msg := fmt.Sprintf("policy %s.%s: %s", er.Policy.GetName(), rule.Name(), rule.Message())
 				warnings = append(warnings, msg)
 			}
 		}
diff --git a/pkg/webhooks/utils/warning_test.go b/pkg/webhooks/utils/warning_test.go
index d5c6e570b1..d7e4c969a2 100644
--- a/pkg/webhooks/utils/warning_test.go
+++ b/pkg/webhooks/utils/warning_test.go
@@ -36,11 +36,7 @@ func TestGetWarningMessages(t *testing.T) {
 				},
 				PolicyResponse: engineapi.PolicyResponse{
 					Rules: []engineapi.RuleResponse{
-						{
-							Name:    "rule",
-							Status:  engineapi.RuleStatusWarn,
-							Message: "message warn",
-						},
+						*engineapi.NewRuleResponse("rule", engineapi.Validation, "message warn", engineapi.RuleStatusWarn),
 					},
 				},
 			},
@@ -59,31 +55,11 @@ func TestGetWarningMessages(t *testing.T) {
 				},
 				PolicyResponse: engineapi.PolicyResponse{
 					Rules: []engineapi.RuleResponse{
-						{
-							Name:    "rule-pass",
-							Status:  engineapi.RuleStatusPass,
-							Message: "message pass",
-						},
-						{
-							Name:    "rule-warn",
-							Status:  engineapi.RuleStatusWarn,
-							Message: "message warn",
-						},
-						{
-							Name:    "rule-fail",
-							Status:  engineapi.RuleStatusFail,
-							Message: "message fail",
-						},
-						{
-							Name:    "rule-error",
-							Status:  engineapi.RuleStatusError,
-							Message: "message error",
-						},
-						{
-							Name:    "rule-skip",
-							Status:  engineapi.RuleStatusSkip,
-							Message: "message skip",
-						},
+						*engineapi.RulePass("rule-pass", engineapi.Validation, "message pass"),
+						*engineapi.NewRuleResponse("rule-warn", engineapi.Validation, "message warn", engineapi.RuleStatusWarn),
+						*engineapi.RuleFail("rule-fail", engineapi.Validation, "message fail"),
+						*engineapi.RuleError("rule-error", engineapi.Validation, "message error", nil),
+						*engineapi.RuleSkip("rule-skip", engineapi.Validation, "message skip"),
 					},
 				},
 			},