From b06dd508e703ab304974ef14ff8816f69a4a8f3a Mon Sep 17 00:00:00 2001
From: Lee Bernick <lee.a.bernick@gmail.com>
Date: Tue, 16 May 2023 16:01:07 -0400
Subject: [PATCH] Cleanup context-based validation of propagated
 params/workspaces

Prior to this commit, a sentinel value was injected into the context
to determine what validation to perform for propagated parameters and workspaces.
This commit removes the sentinel value and calls validation functions
where they are needed. It exports and adds tests for ValidateUsageOfDeclaredParameters
so it can be called in the reconciler. No functional changes.
---
 pkg/apis/config/contexts.go                   |  34 ----
 pkg/apis/pipeline/v1/pipeline_types_test.go   |   5 +-
 pkg/apis/pipeline/v1/pipeline_validation.go   |  15 +-
 .../pipeline/v1/pipeline_validation_test.go   |  11 +-
 .../pipeline/v1/pipelinerun_validation.go     |   3 +-
 pkg/apis/pipeline/v1/task_validation.go       |  17 +-
 pkg/apis/pipeline/v1/task_validation_test.go  | 175 +++++++++++++++++-
 pkg/apis/pipeline/v1/taskrun_validation.go    |   4 +-
 .../v1beta1/cluster_task_validation.go        |   7 +-
 .../pipeline/v1beta1/pipeline_types_test.go   |   6 +-
 .../pipeline/v1beta1/pipeline_validation.go   |  15 +-
 .../v1beta1/pipeline_validation_test.go       |  13 +-
 .../v1beta1/pipelinerun_validation.go         |   3 +-
 pkg/apis/pipeline/v1beta1/task_validation.go  |  17 +-
 .../pipeline/v1beta1/task_validation_test.go  |   6 -
 .../pipeline/v1beta1/taskrun_validation.go    |   4 +-
 pkg/reconciler/pipelinerun/pipelinerun.go     |   3 -
 pkg/reconciler/taskrun/taskrun.go             |  10 +-
 18 files changed, 213 insertions(+), 135 deletions(-)
 delete mode 100644 pkg/apis/config/contexts.go

diff --git a/pkg/apis/config/contexts.go b/pkg/apis/config/contexts.go
deleted file mode 100644
index 8cfb135a3b3..00000000000
--- a/pkg/apis/config/contexts.go
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
-Copyright 2019 The Tekton Authors
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package config
-
-import (
-	"context"
-)
-
-// validatePropagatedVariables is used for deciding whether to validate or skip parameters and workspaces inside the contect.Context.
-type validatePropagatedVariables string
-
-// SkipValidationDueToPropagatedParametersAndWorkspaces sets the context to skip validation of parameters when embedded vs referenced to true or false.
-func SkipValidationDueToPropagatedParametersAndWorkspaces(ctx context.Context, skip bool) context.Context {
-	return context.WithValue(ctx, validatePropagatedVariables("ValidatePropagatedParameterVariablesAndWorkspaces"), !skip)
-}
-
-// ValidateParameterVariablesAndWorkspaces indicates if validation of paramater variables and workspaces should be conducted.
-func ValidateParameterVariablesAndWorkspaces(ctx context.Context) bool {
-	return ctx.Value(validatePropagatedVariables("ValidatePropagatedParameterVariablesAndWorkspaces")) == true
-}
diff --git a/pkg/apis/pipeline/v1/pipeline_types_test.go b/pkg/apis/pipeline/v1/pipeline_types_test.go
index d0249b463ff..10a6c42c039 100644
--- a/pkg/apis/pipeline/v1/pipeline_types_test.go
+++ b/pkg/apis/pipeline/v1/pipeline_types_test.go
@@ -233,7 +233,6 @@ func TestPipelineTask_ValidateRegularTask_Success(t *testing.T) {
 				cfg.FeatureFlags.EnableAPIFields = config.BetaAPIFields
 			}
 			ctx = config.ToContext(ctx, cfg)
-			ctx = config.SkipValidationDueToPropagatedParametersAndWorkspaces(ctx, false)
 			err := tt.tasks.validateTask(ctx)
 			if err != nil {
 				t.Errorf("PipelineTask.validateTask() returned error for valid pipeline task: %v", err)
@@ -292,8 +291,7 @@ func TestPipelineTask_ValidateRegularTask_Failure(t *testing.T) {
 	}}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			ctx := config.SkipValidationDueToPropagatedParametersAndWorkspaces(context.Background(), false)
-			err := tt.task.validateTask(ctx)
+			err := tt.task.validateTask(context.Background())
 			if err == nil {
 				t.Error("PipelineTask.validateTask() did not return error for invalid pipeline task")
 			}
@@ -346,7 +344,6 @@ func TestPipelineTask_Validate_Failure(t *testing.T) {
 			if tt.wc != nil {
 				ctx = tt.wc(ctx)
 			}
-			ctx = config.SkipValidationDueToPropagatedParametersAndWorkspaces(ctx, false)
 			err := tt.p.Validate(ctx)
 			if err == nil {
 				t.Error("PipelineTask.Validate() did not return error for invalid pipeline task")
diff --git a/pkg/apis/pipeline/v1/pipeline_validation.go b/pkg/apis/pipeline/v1/pipeline_validation.go
index 4a426ebf0c3..5b29b5af4ed 100644
--- a/pkg/apis/pipeline/v1/pipeline_validation.go
+++ b/pkg/apis/pipeline/v1/pipeline_validation.go
@@ -46,8 +46,12 @@ func (p *Pipeline) SupportedVerbs() []admissionregistrationv1.OperationType {
 // that any references resources exist, that is done at run time.
 func (p *Pipeline) Validate(ctx context.Context) *apis.FieldError {
 	errs := validate.ObjectMetadata(p.GetObjectMeta()).ViaField("metadata")
-	ctx = config.SkipValidationDueToPropagatedParametersAndWorkspaces(ctx, false)
-	return errs.Also(p.Spec.Validate(apis.WithinSpec(ctx)).ViaField("spec"))
+	errs = errs.Also(p.Spec.Validate(apis.WithinSpec(ctx)).ViaField("spec"))
+	// When a Pipeline is created directly, instead of declared inline in a PipelineRun,
+	// we do not support propagated parameters and workspaces.
+	// Validate that all params and workspaces it uses are declared.
+	errs = errs.Also(p.Spec.validatePipelineParameterUsage().ViaField("spec"))
+	return errs.Also(p.Spec.validatePipelineWorkspacesUsage().ViaField("spec"))
 }
 
 // Validate checks that taskNames in the Pipeline are valid and that the graph
@@ -77,13 +81,6 @@ func (ps *PipelineSpec) Validate(ctx context.Context) (errs *apis.FieldError) {
 	errs = errs.Also(validateMatrix(ctx, ps.Tasks).ViaField("tasks"))
 	errs = errs.Also(validateMatrix(ctx, ps.Finally).ViaField("finally"))
 	errs = errs.Also(validateResultsFromMatrixedPipelineTasksNotConsumed(ps.Tasks, ps.Finally))
-	// When propagating params and workspaces, params and workspaces used in the Pipeline spec may not be declared by the Pipeline.
-	// Only perform this validation after all declared params and workspaces have been propagated.
-	// TODO(#6647): Remove this flag and call this function in the reconciler instead
-	if config.ValidateParameterVariablesAndWorkspaces(ctx) {
-		errs = errs.Also(ps.validatePipelineParameterUsage())
-		errs = errs.Also(ps.validatePipelineWorkspacesUsage())
-	}
 	return errs
 }
 
diff --git a/pkg/apis/pipeline/v1/pipeline_validation_test.go b/pkg/apis/pipeline/v1/pipeline_validation_test.go
index a039914dba9..f3ea1f762c0 100644
--- a/pkg/apis/pipeline/v1/pipeline_validation_test.go
+++ b/pkg/apis/pipeline/v1/pipeline_validation_test.go
@@ -614,8 +614,7 @@ func TestPipelineSpec_Validate_Failure(t *testing.T) {
 	}}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			ctx := config.SkipValidationDueToPropagatedParametersAndWorkspaces(context.Background(), false)
-			err := tt.ps.Validate(ctx)
+			err := tt.ps.Validate(context.Background())
 			if err == nil {
 				t.Errorf("PipelineSpec.Validate() did not return error for invalid pipelineSpec")
 			}
@@ -637,8 +636,7 @@ func TestPipelineSpec_Validate_Failure_CycleDAG(t *testing.T) {
 			Name: "baz", TaskRef: &TaskRef{Name: "baz-task"}, RunAfter: []string{"bar"},
 		}},
 	}
-	ctx := config.SkipValidationDueToPropagatedParametersAndWorkspaces(context.Background(), false)
-	err := ps.Validate(ctx)
+	err := ps.Validate(context.Background())
 	if err == nil {
 		t.Errorf("PipelineSpec.Validate() did not return error for invalid pipelineSpec: %s", name)
 	}
@@ -705,8 +703,7 @@ func TestValidatePipelineTasks_Failure(t *testing.T) {
 	}}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			ctx := config.SkipValidationDueToPropagatedParametersAndWorkspaces(context.Background(), false)
-			err := ValidatePipelineTasks(ctx, tt.tasks, tt.finalTasks)
+			err := ValidatePipelineTasks(context.Background(), tt.tasks, tt.finalTasks)
 			if err == nil {
 				t.Error("ValidatePipelineTasks() did not return error for invalid pipeline tasks")
 			}
@@ -1293,7 +1290,6 @@ func TestValidatePipelineParameterVariables_Success(t *testing.T) {
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			ctx := config.EnableAlphaAPIFields(context.Background())
-			ctx = config.SkipValidationDueToPropagatedParametersAndWorkspaces(ctx, false)
 			err := ValidatePipelineParameterVariables(ctx, tt.tasks, tt.params)
 			if err != nil {
 				t.Errorf("Pipeline.ValidatePipelineParameterVariables() returned error for valid pipeline parameters: %v", err)
@@ -3039,7 +3035,6 @@ func TestMatrixIncompatibleAPIVersions(t *testing.T) {
 				ctx := config.ToContext(context.Background(), cfg)
 
 				ps.SetDefaults(ctx)
-				ctx = config.SkipValidationDueToPropagatedParametersAndWorkspaces(ctx, false)
 				err := ps.Validate(ctx)
 
 				if tt.requiredVersion != version && err == nil {
diff --git a/pkg/apis/pipeline/v1/pipelinerun_validation.go b/pkg/apis/pipeline/v1/pipelinerun_validation.go
index a2057a14753..e6c7299c8e1 100644
--- a/pkg/apis/pipeline/v1/pipelinerun_validation.go
+++ b/pkg/apis/pipeline/v1/pipelinerun_validation.go
@@ -66,7 +66,6 @@ func (ps *PipelineRunSpec) Validate(ctx context.Context) (errs *apis.FieldError)
 
 	// Validate PipelineSpec if it's present
 	if ps.PipelineSpec != nil {
-		ctx = config.SkipValidationDueToPropagatedParametersAndWorkspaces(ctx, true)
 		errs = errs.Also(ps.PipelineSpec.Validate(ctx).ViaField("pipelineSpec"))
 	}
 
@@ -180,7 +179,7 @@ func (ps *PipelineRunSpec) validateInlineParameters(ctx context.Context) (errs *
 			if pt.TaskSpec != nil && pt.TaskSpec.Steps != nil {
 				errs = errs.Also(ValidateParameterTypes(ctx, paramSpec))
 				errs = errs.Also(ValidateParameterVariables(ctx, pt.TaskSpec.Steps, paramSpec))
-				errs = errs.Also(validateUsageOfDeclaredParameters(ctx, pt.TaskSpec.Steps, paramSpec))
+				errs = errs.Also(ValidateUsageOfDeclaredParameters(ctx, pt.TaskSpec.Steps, paramSpec))
 			}
 		}
 		errs = errs.Also(ValidatePipelineParameterVariables(ctx, ps.PipelineSpec.Tasks, paramSpec))
diff --git a/pkg/apis/pipeline/v1/task_validation.go b/pkg/apis/pipeline/v1/task_validation.go
index cb5e9955390..600dc8c278b 100644
--- a/pkg/apis/pipeline/v1/task_validation.go
+++ b/pkg/apis/pipeline/v1/task_validation.go
@@ -62,8 +62,10 @@ var objectVariableNameFormatRegex = regexp.MustCompile(objectVariableNameFormat)
 // Validate implements apis.Validatable
 func (t *Task) Validate(ctx context.Context) *apis.FieldError {
 	errs := validate.ObjectMetadata(t.GetObjectMeta()).ViaField("metadata")
-	ctx = config.SkipValidationDueToPropagatedParametersAndWorkspaces(ctx, false)
-	return errs.Also(t.Spec.Validate(apis.WithinSpec(ctx)).ViaField("spec"))
+	errs = errs.Also(t.Spec.Validate(apis.WithinSpec(ctx)).ViaField("spec"))
+	// When a Task is created directly, instead of declared inline in a TaskRun or PipelineRun,
+	// we do not support propagated parameters. Validate that all params it uses are declared.
+	return errs.Also(ValidateUsageOfDeclaredParameters(ctx, t.Spec.Steps, t.Spec.Params).ViaField("spec"))
 }
 
 // Validate implements apis.Validatable
@@ -72,13 +74,6 @@ func (ts *TaskSpec) Validate(ctx context.Context) (errs *apis.FieldError) {
 		errs = errs.Also(apis.ErrMissingField("steps"))
 	}
 
-	// When propagating parameters, parameters used in the Task spec may not be declared by the Task.
-	// Only perform this validation after all declared parameters have been propagated.
-	// TODO(#6647): Remove this flag and call this function in the reconciler instead
-	if config.ValidateParameterVariablesAndWorkspaces(ctx) {
-		errs = errs.Also(validateUsageOfDeclaredParameters(ctx, ts.Steps, ts.Params))
-	}
-
 	errs = errs.Also(ValidateVolumes(ts.Volumes).ViaField("volumes"))
 	errs = errs.Also(validateDeclaredWorkspaces(ts.Workspaces, ts.Steps, ts.StepTemplate).ViaField("workspaces"))
 	errs = errs.Also(validateWorkspaceUsages(ctx, ts))
@@ -102,8 +97,8 @@ func (ts *TaskSpec) Validate(ctx context.Context) (errs *apis.FieldError) {
 	return errs
 }
 
-// validateUsageOfDeclaredParameters validates that all parameters referenced in the Task are declared by the Task.
-func validateUsageOfDeclaredParameters(ctx context.Context, steps []Step, params ParamSpecs) *apis.FieldError {
+// ValidateUsageOfDeclaredParameters validates that all parameters referenced in the Task are declared by the Task.
+func ValidateUsageOfDeclaredParameters(ctx context.Context, steps []Step, params ParamSpecs) *apis.FieldError {
 	var errs *apis.FieldError
 	_, _, objectParams := params.sortByType()
 	allParameterNames := sets.NewString(params.getNames()...)
diff --git a/pkg/apis/pipeline/v1/task_validation_test.go b/pkg/apis/pipeline/v1/task_validation_test.go
index d625b794308..7178b24a252 100644
--- a/pkg/apis/pipeline/v1/task_validation_test.go
+++ b/pkg/apis/pipeline/v1/task_validation_test.go
@@ -141,7 +141,6 @@ func TestTaskSpecValidatePropagatedParamsAndWorkspaces(t *testing.T) {
 			}
 			ctx := config.EnableBetaAPIFields(context.Background())
 			ts.SetDefaults(ctx)
-			ctx = config.SkipValidationDueToPropagatedParametersAndWorkspaces(ctx, true)
 			if err := ts.Validate(ctx); err != nil {
 				t.Errorf("TaskSpec.Validate() = %v", err)
 			}
@@ -718,7 +717,6 @@ func TestTaskValidateError(t *testing.T) {
 				}}
 			ctx := config.EnableAlphaAPIFields(context.Background())
 			task.SetDefaults(ctx)
-			ctx = config.SkipValidationDueToPropagatedParametersAndWorkspaces(ctx, false)
 			err := task.Validate(ctx)
 			if err == nil {
 				t.Fatalf("Expected an error, got nothing for %v", task)
@@ -1352,7 +1350,7 @@ func TestTaskSpecValidateError(t *testing.T) {
 	}}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			ts := &v1.TaskSpec{
+			ts := v1.TaskSpec{
 				Params:       tt.fields.Params,
 				Steps:        tt.fields.Steps,
 				Volumes:      tt.fields.Volumes,
@@ -1445,7 +1443,6 @@ func TestStepAndSidecarWorkspaces(t *testing.T) {
 			}
 			ctx := config.EnableAlphaAPIFields(context.Background())
 			ts.SetDefaults(ctx)
-			ctx = config.SkipValidationDueToPropagatedParametersAndWorkspaces(ctx, true)
 			if err := ts.Validate(ctx); err != nil {
 				t.Errorf("TaskSpec.Validate() = %v", err)
 			}
@@ -1503,7 +1500,6 @@ func TestStepAndSidecarWorkspacesErrors(t *testing.T) {
 
 			ctx := config.EnableAlphaAPIFields(context.Background())
 			ts.SetDefaults(ctx)
-			ctx = config.SkipValidationDueToPropagatedParametersAndWorkspaces(ctx, true)
 			err := ts.Validate(ctx)
 			if err == nil {
 				t.Fatalf("Expected an error, got nothing for %v", ts)
@@ -1568,7 +1564,6 @@ func TestStepOnError(t *testing.T) {
 			}
 			ctx := context.Background()
 			ts.SetDefaults(ctx)
-			ctx = config.SkipValidationDueToPropagatedParametersAndWorkspaces(ctx, false)
 			err := ts.Validate(ctx)
 			if tt.expectedError == nil && err != nil {
 				t.Errorf("No error expected from TaskSpec.Validate() but got = %v", err)
@@ -1666,7 +1661,6 @@ func TestIncompatibleAPIVersions(t *testing.T) {
 					ctx = config.EnableAlphaAPIFields(ctx)
 				}
 				ts.SetDefaults(ctx)
-				ctx = config.SkipValidationDueToPropagatedParametersAndWorkspaces(ctx, true)
 				err := ts.Validate(ctx)
 
 				if tt.requiredVersion != version && err == nil {
@@ -1906,6 +1900,173 @@ func TestTaskSpecBetaFields(t *testing.T) {
 	}
 }
 
+func TestTaskSpecValidateUsageOfDeclaredParams(t *testing.T) {
+	tests := []struct {
+		name          string
+		Params        []v1.ParamSpec
+		Steps         []v1.Step
+		expectedError apis.FieldError
+	}{{
+		name: "inexistent param variable",
+		Steps: []v1.Step{{
+			Name:  "mystep",
+			Image: "myimage",
+			Args:  []string{"--flag=$(params.inexistent)"},
+		}},
+		expectedError: apis.FieldError{
+			Message: `non-existent variable in "--flag=$(params.inexistent)"`,
+			Paths:   []string{"steps[0].args[0]"},
+		},
+	}, {
+		name: "object used in a string field",
+		Params: []v1.ParamSpec{{
+			Name: "gitrepo",
+			Type: v1.ParamTypeObject,
+			Properties: map[string]v1.PropertySpec{
+				"url":    {},
+				"commit": {},
+			},
+		}},
+		Steps: []v1.Step{{
+			Name:       "do-the-clone",
+			Image:      "$(params.gitrepo)",
+			Args:       []string{"echo"},
+			WorkingDir: "/foo/bar/src/",
+		}},
+		expectedError: apis.FieldError{
+			Message: `variable type invalid in "$(params.gitrepo)"`,
+			Paths:   []string{"steps[0].image"},
+		},
+	}, {
+		name: "object star used in a string field",
+		Params: []v1.ParamSpec{{
+			Name: "gitrepo",
+			Type: v1.ParamTypeObject,
+			Properties: map[string]v1.PropertySpec{
+				"url":    {},
+				"commit": {},
+			},
+		}},
+		Steps: []v1.Step{{
+			Name:       "do-the-clone",
+			Image:      "$(params.gitrepo[*])",
+			Args:       []string{"echo"},
+			WorkingDir: "/foo/bar/src/",
+		}},
+		expectedError: apis.FieldError{
+			Message: `variable type invalid in "$(params.gitrepo[*])"`,
+			Paths:   []string{"steps[0].image"},
+		},
+	}, {
+		name: "object used in a field that can accept array type",
+		Params: []v1.ParamSpec{{
+			Name: "gitrepo",
+			Type: v1.ParamTypeObject,
+			Properties: map[string]v1.PropertySpec{
+				"url":    {},
+				"commit": {},
+			},
+		}},
+		Steps: []v1.Step{{
+			Name:       "do-the-clone",
+			Image:      "myimage",
+			Args:       []string{"$(params.gitrepo)"},
+			WorkingDir: "/foo/bar/src/",
+		}},
+		expectedError: apis.FieldError{
+			Message: `variable type invalid in "$(params.gitrepo)"`,
+			Paths:   []string{"steps[0].args[0]"},
+		},
+	}, {
+		name: "object star used in a field that can accept array type",
+		Params: []v1.ParamSpec{{
+			Name: "gitrepo",
+			Type: v1.ParamTypeObject,
+			Properties: map[string]v1.PropertySpec{
+				"url":    {},
+				"commit": {},
+			},
+		}},
+		Steps: []v1.Step{{
+			Name:       "do-the-clone",
+			Image:      "some-git-image",
+			Args:       []string{"$(params.gitrepo[*])"},
+			WorkingDir: "/foo/bar/src/",
+		}},
+		expectedError: apis.FieldError{
+			Message: `variable type invalid in "$(params.gitrepo[*])"`,
+			Paths:   []string{"steps[0].args[0]"},
+		},
+	}, {
+		name: "non-existent individual key of an object param is used in task step",
+		Params: []v1.ParamSpec{{
+			Name: "gitrepo",
+			Type: v1.ParamTypeObject,
+			Properties: map[string]v1.PropertySpec{
+				"url":    {},
+				"commit": {},
+			},
+		}},
+		Steps: []v1.Step{{
+			Name:       "do-the-clone",
+			Image:      "some-git-image",
+			Args:       []string{"$(params.gitrepo.non-exist-key)"},
+			WorkingDir: "/foo/bar/src/",
+		}},
+		expectedError: apis.FieldError{
+			Message: `non-existent variable in "$(params.gitrepo.non-exist-key)"`,
+			Paths:   []string{"steps[0].args[0]"},
+		},
+	}, {
+		name: "Inexistent param variable in volumeMount with existing",
+		Params: []v1.ParamSpec{
+			{
+				Name:        "foo",
+				Description: "param",
+				Default:     v1.NewStructuredValues("default"),
+			},
+		},
+		Steps: []v1.Step{{
+			Name:  "mystep",
+			Image: "myimage",
+			VolumeMounts: []corev1.VolumeMount{{
+				Name: "$(params.inexistent)-foo",
+			}},
+		}},
+		expectedError: apis.FieldError{
+			Message: `non-existent variable in "$(params.inexistent)-foo"`,
+			Paths:   []string{"steps[0].volumeMount[0].name"},
+		},
+	}, {
+		name: "Inexistent param variable with existing",
+		Params: []v1.ParamSpec{{
+			Name:        "foo",
+			Description: "param",
+			Default:     v1.NewStructuredValues("default"),
+		}},
+		Steps: []v1.Step{{
+			Name:  "mystep",
+			Image: "myimage",
+			Args:  []string{"$(params.foo) && $(params.inexistent)"},
+		}},
+		expectedError: apis.FieldError{
+			Message: `non-existent variable in "$(params.foo) && $(params.inexistent)"`,
+			Paths:   []string{"steps[0].args[0]"},
+		},
+	}}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			err := v1.ValidateUsageOfDeclaredParameters(context.Background(), tt.Steps, tt.Params)
+			if err == nil {
+				t.Fatalf("Expected an error, got nothing")
+			}
+			if d := cmp.Diff(tt.expectedError.Error(), err.Error(), cmpopts.IgnoreUnexported(apis.FieldError{})); d != "" {
+				t.Errorf("TaskSpec.Validate() errors diff %s", diff.PrintWantGot(d))
+			}
+		})
+	}
+}
+
 func TestGetArrayIndexParamRefs(t *testing.T) {
 	stepsReferences := []string{}
 	for i := 10; i <= 26; i++ {
diff --git a/pkg/apis/pipeline/v1/taskrun_validation.go b/pkg/apis/pipeline/v1/taskrun_validation.go
index a71b96ba42a..7fb04691e98 100644
--- a/pkg/apis/pipeline/v1/taskrun_validation.go
+++ b/pkg/apis/pipeline/v1/taskrun_validation.go
@@ -62,8 +62,6 @@ func (ts *TaskRunSpec) Validate(ctx context.Context) (errs *apis.FieldError) {
 	}
 	// Validate TaskSpec if it's present.
 	if ts.TaskSpec != nil {
-		// skip validation of parameter and workspaces variables since we validate them via taskrunspec below.
-		ctx = config.SkipValidationDueToPropagatedParametersAndWorkspaces(ctx, true)
 		errs = errs.Also(ts.TaskSpec.Validate(ctx).ViaField("taskSpec"))
 	}
 
@@ -142,7 +140,7 @@ func (ts *TaskRunSpec) validateInlineParameters(ctx context.Context) (errs *apis
 	if ts.TaskSpec != nil && ts.TaskSpec.Steps != nil {
 		errs = errs.Also(ValidateParameterTypes(ctx, paramSpec))
 		errs = errs.Also(ValidateParameterVariables(ctx, ts.TaskSpec.Steps, paramSpec))
-		errs = errs.Also(validateUsageOfDeclaredParameters(ctx, ts.TaskSpec.Steps, paramSpec))
+		errs = errs.Also(ValidateUsageOfDeclaredParameters(ctx, ts.TaskSpec.Steps, paramSpec))
 	}
 	return errs
 }
diff --git a/pkg/apis/pipeline/v1beta1/cluster_task_validation.go b/pkg/apis/pipeline/v1beta1/cluster_task_validation.go
index 342f3717be4..5dfa6278c48 100644
--- a/pkg/apis/pipeline/v1beta1/cluster_task_validation.go
+++ b/pkg/apis/pipeline/v1beta1/cluster_task_validation.go
@@ -19,7 +19,6 @@ package v1beta1
 import (
 	"context"
 
-	"github.com/tektoncd/pipeline/pkg/apis/config"
 	"github.com/tektoncd/pipeline/pkg/apis/validate"
 	"knative.dev/pkg/apis"
 )
@@ -32,6 +31,8 @@ func (t *ClusterTask) Validate(ctx context.Context) *apis.FieldError {
 		return nil
 	}
 	errs := validate.ObjectMetadata(t.GetObjectMeta()).ViaField("metadata")
-	ctx = config.SkipValidationDueToPropagatedParametersAndWorkspaces(ctx, false)
-	return errs.Also(t.Spec.Validate(apis.WithinSpec(ctx)).ViaField("spec"))
+	errs = errs.Also(t.Spec.Validate(apis.WithinSpec(ctx)).ViaField("spec"))
+	// We do not support propagated parameters in ClusterTasks.
+	// Validate that all params the ClusterTask uses are declared.
+	return errs.Also(ValidateUsageOfDeclaredParameters(ctx, t.Spec.Steps, t.Spec.Params))
 }
diff --git a/pkg/apis/pipeline/v1beta1/pipeline_types_test.go b/pkg/apis/pipeline/v1beta1/pipeline_types_test.go
index f137ae152a6..327aed8a2b6 100644
--- a/pkg/apis/pipeline/v1beta1/pipeline_types_test.go
+++ b/pkg/apis/pipeline/v1beta1/pipeline_types_test.go
@@ -266,7 +266,6 @@ func TestPipelineTask_ValidateRegularTask_Success(t *testing.T) {
 				cfg.FeatureFlags.EnableTektonOCIBundles = true
 			}
 			ctx = config.ToContext(ctx, cfg)
-			ctx = config.SkipValidationDueToPropagatedParametersAndWorkspaces(ctx, false)
 			err := tt.tasks.validateTask(ctx)
 			if err != nil {
 				t.Errorf("PipelineTask.validateTask() returned error for valid pipeline task: %v", err)
@@ -320,8 +319,7 @@ func TestPipelineTask_ValidateRegularTask_Failure(t *testing.T) {
 	}}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			ctx := config.SkipValidationDueToPropagatedParametersAndWorkspaces(context.Background(), false)
-			err := tt.task.validateTask(ctx)
+			err := tt.task.validateTask(context.Background())
 			if err == nil {
 				t.Error("PipelineTask.validateTask() did not return error for invalid pipeline task")
 			}
@@ -384,7 +382,6 @@ func TestPipelineTask_Validate_Failure(t *testing.T) {
 			if tt.wc != nil {
 				ctx = tt.wc(ctx)
 			}
-			ctx = config.SkipValidationDueToPropagatedParametersAndWorkspaces(ctx, false)
 			err := tt.p.Validate(ctx)
 			if err == nil {
 				t.Error("PipelineTask.Validate() did not return error for invalid pipeline task")
@@ -568,7 +565,6 @@ func TestPipelineTaskList_Validate(t *testing.T) {
 				ctx = tt.wc(ctx)
 			}
 			taskNames := sets.String{}
-			ctx = config.SkipValidationDueToPropagatedParametersAndWorkspaces(ctx, false)
 			err := tt.tasks.Validate(ctx, taskNames, tt.path)
 			if tt.expectedError != nil && err == nil {
 				t.Error("PipelineTaskList.Validate() did not return error for invalid pipeline tasks")
diff --git a/pkg/apis/pipeline/v1beta1/pipeline_validation.go b/pkg/apis/pipeline/v1beta1/pipeline_validation.go
index 33f27701af7..59996d17af2 100644
--- a/pkg/apis/pipeline/v1beta1/pipeline_validation.go
+++ b/pkg/apis/pipeline/v1beta1/pipeline_validation.go
@@ -47,8 +47,12 @@ func (p *Pipeline) SupportedVerbs() []admissionregistrationv1.OperationType {
 // that any references resources exist, that is done at run time.
 func (p *Pipeline) Validate(ctx context.Context) *apis.FieldError {
 	errs := validate.ObjectMetadata(p.GetObjectMeta()).ViaField("metadata")
-	ctx = config.SkipValidationDueToPropagatedParametersAndWorkspaces(ctx, false)
-	return errs.Also(p.Spec.Validate(apis.WithinSpec(ctx)).ViaField("spec"))
+	errs = errs.Also(p.Spec.Validate(apis.WithinSpec(ctx)).ViaField("spec"))
+	// When a Pipeline is created directly, instead of declared inline in a PipelineRun,
+	// we do not support propagated parameters and workspaces.
+	// Validate that all params and workspaces it uses are declared.
+	errs = errs.Also(p.Spec.validatePipelineParameterUsage().ViaField("spec"))
+	return errs.Also(p.Spec.validatePipelineWorkspacesUsage().ViaField("spec"))
 }
 
 // Validate checks that taskNames in the Pipeline are valid and that the graph
@@ -80,13 +84,6 @@ func (ps *PipelineSpec) Validate(ctx context.Context) (errs *apis.FieldError) {
 	errs = errs.Also(validateMatrix(ctx, ps.Tasks).ViaField("tasks"))
 	errs = errs.Also(validateMatrix(ctx, ps.Finally).ViaField("finally"))
 	errs = errs.Also(validateResultsFromMatrixedPipelineTasksNotConsumed(ps.Tasks, ps.Finally))
-	// When propagating params and workspaces, params and workspaces used in the Pipeline spec may not be declared by the Pipeline.
-	// Only perform this validation after all declared params and workspaces have been propagated.
-	// TODO(#6647): Remove this flag and call this function in the reconciler instead
-	if config.ValidateParameterVariablesAndWorkspaces(ctx) {
-		errs = errs.Also(ps.validatePipelineParameterUsage())
-		errs = errs.Also(ps.validatePipelineWorkspacesUsage())
-	}
 	return errs
 }
 
diff --git a/pkg/apis/pipeline/v1beta1/pipeline_validation_test.go b/pkg/apis/pipeline/v1beta1/pipeline_validation_test.go
index b269844caec..215698ecb79 100644
--- a/pkg/apis/pipeline/v1beta1/pipeline_validation_test.go
+++ b/pkg/apis/pipeline/v1beta1/pipeline_validation_test.go
@@ -657,8 +657,7 @@ func TestPipelineSpec_Validate_Failure(t *testing.T) {
 	}}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			ctx := config.SkipValidationDueToPropagatedParametersAndWorkspaces(context.Background(), false)
-			err := tt.ps.Validate(ctx)
+			err := tt.ps.Validate(context.Background())
 			if err == nil {
 				t.Errorf("PipelineSpec.Validate() did not return error for invalid pipelineSpec")
 			}
@@ -680,8 +679,7 @@ func TestPipelineSpec_Validate_Failure_CycleDAG(t *testing.T) {
 			Name: "baz", TaskRef: &TaskRef{Name: "baz-task"}, RunAfter: []string{"bar"},
 		}},
 	}
-	ctx := config.SkipValidationDueToPropagatedParametersAndWorkspaces(context.Background(), false)
-	err := ps.Validate(ctx)
+	err := ps.Validate(context.Background())
 	if err == nil {
 		t.Errorf("PipelineSpec.Validate() did not return error for invalid pipelineSpec: %s", name)
 	}
@@ -748,8 +746,7 @@ func TestValidatePipelineTasks_Failure(t *testing.T) {
 	}}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			ctx := config.SkipValidationDueToPropagatedParametersAndWorkspaces(context.Background(), false)
-			err := ValidatePipelineTasks(ctx, tt.tasks, tt.finalTasks)
+			err := ValidatePipelineTasks(context.Background(), tt.tasks, tt.finalTasks)
 			if err == nil {
 				t.Error("ValidatePipelineTasks() did not return error for invalid pipeline tasks")
 			}
@@ -1335,8 +1332,7 @@ func TestValidatePipelineParameterVariables_Success(t *testing.T) {
 	}}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			ctx := config.SkipValidationDueToPropagatedParametersAndWorkspaces(context.Background(), false)
-			err := ValidatePipelineParameterVariables(ctx, tt.tasks, tt.params)
+			err := ValidatePipelineParameterVariables(context.Background(), tt.tasks, tt.params)
 			if err != nil {
 				t.Errorf("Pipeline.ValidatePipelineParameterVariables() returned error for valid pipeline parameters: %v", err)
 			}
@@ -3082,7 +3078,6 @@ func TestMatrixIncompatibleAPIVersions(t *testing.T) {
 				ctx := config.ToContext(context.Background(), cfg)
 
 				ps.SetDefaults(ctx)
-				ctx = config.SkipValidationDueToPropagatedParametersAndWorkspaces(ctx, false)
 				err := ps.Validate(ctx)
 
 				if tt.requiredVersion != version && err == nil {
diff --git a/pkg/apis/pipeline/v1beta1/pipelinerun_validation.go b/pkg/apis/pipeline/v1beta1/pipelinerun_validation.go
index 8146582ee4a..307f7b8f2e6 100644
--- a/pkg/apis/pipeline/v1beta1/pipelinerun_validation.go
+++ b/pkg/apis/pipeline/v1beta1/pipelinerun_validation.go
@@ -71,7 +71,6 @@ func (ps *PipelineRunSpec) Validate(ctx context.Context) (errs *apis.FieldError)
 
 	// Validate PipelineSpec if it's present
 	if ps.PipelineSpec != nil {
-		ctx = config.SkipValidationDueToPropagatedParametersAndWorkspaces(ctx, true)
 		errs = errs.Also(ps.PipelineSpec.Validate(ctx).ViaField("pipelineSpec"))
 	}
 
@@ -233,7 +232,7 @@ func (ps *PipelineRunSpec) validateInlineParameters(ctx context.Context) (errs *
 			if pt.TaskSpec != nil && pt.TaskSpec.Steps != nil {
 				errs = errs.Also(ValidateParameterTypes(ctx, paramSpec))
 				errs = errs.Also(ValidateParameterVariables(ctx, pt.TaskSpec.Steps, paramSpec))
-				errs = errs.Also(validateUsageOfDeclaredParameters(ctx, pt.TaskSpec.Steps, paramSpec))
+				errs = errs.Also(ValidateUsageOfDeclaredParameters(ctx, pt.TaskSpec.Steps, paramSpec))
 			}
 		}
 		errs = errs.Also(ValidatePipelineParameterVariables(ctx, ps.PipelineSpec.Tasks, paramSpec))
diff --git a/pkg/apis/pipeline/v1beta1/task_validation.go b/pkg/apis/pipeline/v1beta1/task_validation.go
index 00386bd5a9f..9d289544a34 100644
--- a/pkg/apis/pipeline/v1beta1/task_validation.go
+++ b/pkg/apis/pipeline/v1beta1/task_validation.go
@@ -62,8 +62,10 @@ var objectVariableNameFormatRegex = regexp.MustCompile(objectVariableNameFormat)
 // Validate implements apis.Validatable
 func (t *Task) Validate(ctx context.Context) *apis.FieldError {
 	errs := validate.ObjectMetadata(t.GetObjectMeta()).ViaField("metadata")
-	ctx = config.SkipValidationDueToPropagatedParametersAndWorkspaces(ctx, false)
-	return errs.Also(t.Spec.Validate(apis.WithinSpec(ctx)).ViaField("spec"))
+	errs = errs.Also(t.Spec.Validate(apis.WithinSpec(ctx)).ViaField("spec"))
+	// When a Task is created directly, instead of declared inline in a TaskRun or PipelineRun,
+	// we do not support propagated parameters. Validate that all params it uses are declared.
+	return errs.Also(ValidateUsageOfDeclaredParameters(ctx, t.Spec.Steps, t.Spec.Params).ViaField("spec"))
 }
 
 // Validate implements apis.Validatable
@@ -72,13 +74,6 @@ func (ts *TaskSpec) Validate(ctx context.Context) (errs *apis.FieldError) {
 		errs = errs.Also(apis.ErrMissingField("steps"))
 	}
 
-	// When propagating parameters, parameters used in the Task spec may not be declared by the Task.
-	// Only perform this validation after all declared parameters have been propagated.
-	// TODO(#6647): Remove this flag and call this function in the reconciler instead
-	if config.ValidateParameterVariablesAndWorkspaces(ctx) {
-		errs = errs.Also(validateUsageOfDeclaredParameters(ctx, ts.Steps, ts.Params))
-	}
-
 	errs = errs.Also(ValidateVolumes(ts.Volumes).ViaField("volumes"))
 	errs = errs.Also(validateDeclaredWorkspaces(ts.Workspaces, ts.Steps, ts.StepTemplate).ViaField("workspaces"))
 	errs = errs.Also(validateWorkspaceUsages(ctx, ts))
@@ -104,8 +99,8 @@ func (ts *TaskSpec) Validate(ctx context.Context) (errs *apis.FieldError) {
 	return errs
 }
 
-// validateUsageOfDeclaredParameters validates that all parameters referenced in the Task are declared by the Task.
-func validateUsageOfDeclaredParameters(ctx context.Context, steps []Step, params ParamSpecs) *apis.FieldError {
+// ValidateUsageOfDeclaredParameters validates that all parameters referenced in the Task are declared by the Task.
+func ValidateUsageOfDeclaredParameters(ctx context.Context, steps []Step, params ParamSpecs) *apis.FieldError {
 	var errs *apis.FieldError
 	_, _, objectParams := params.sortByType()
 	allParameterNames := sets.NewString(params.getNames()...)
diff --git a/pkg/apis/pipeline/v1beta1/task_validation_test.go b/pkg/apis/pipeline/v1beta1/task_validation_test.go
index 7e0fbd5f77b..93d69216fc8 100644
--- a/pkg/apis/pipeline/v1beta1/task_validation_test.go
+++ b/pkg/apis/pipeline/v1beta1/task_validation_test.go
@@ -144,7 +144,6 @@ func TestTaskSpecValidatePropagatedParamsAndWorkspaces(t *testing.T) {
 			}
 			ctx := context.Background()
 			ts.SetDefaults(ctx)
-			ctx = config.SkipValidationDueToPropagatedParametersAndWorkspaces(ctx, true)
 			if err := ts.Validate(ctx); err != nil {
 				t.Errorf("TaskSpec.Validate() = %v", err)
 			}
@@ -721,7 +720,6 @@ func TestTaskValidateError(t *testing.T) {
 				}}
 			ctx := config.EnableAlphaAPIFields(context.Background())
 			task.SetDefaults(ctx)
-			ctx = config.SkipValidationDueToPropagatedParametersAndWorkspaces(ctx, false)
 			err := task.Validate(ctx)
 			if err == nil {
 				t.Fatalf("Expected an error, got nothing for %v", task)
@@ -1457,7 +1455,6 @@ func TestStepAndSidecarWorkspaces(t *testing.T) {
 			}
 			ctx := config.EnableAlphaAPIFields(context.Background())
 			ts.SetDefaults(ctx)
-			ctx = config.SkipValidationDueToPropagatedParametersAndWorkspaces(ctx, true)
 			if err := ts.Validate(ctx); err != nil {
 				t.Errorf("TaskSpec.Validate() = %v", err)
 			}
@@ -1515,7 +1512,6 @@ func TestStepAndSidecarWorkspacesErrors(t *testing.T) {
 
 			ctx := config.EnableAlphaAPIFields(context.Background())
 			ts.SetDefaults(ctx)
-			ctx = config.SkipValidationDueToPropagatedParametersAndWorkspaces(ctx, true)
 			err := ts.Validate(ctx)
 			if err == nil {
 				t.Fatalf("Expected an error, got nothing for %v", ts)
@@ -1580,7 +1576,6 @@ func TestStepOnError(t *testing.T) {
 			}
 			ctx := context.Background()
 			ts.SetDefaults(ctx)
-			ctx = config.SkipValidationDueToPropagatedParametersAndWorkspaces(ctx, false)
 			err := ts.Validate(ctx)
 			if tt.expectedError == nil && err != nil {
 				t.Errorf("No error expected from TaskSpec.Validate() but got = %v", err)
@@ -1678,7 +1673,6 @@ func TestIncompatibleAPIVersions(t *testing.T) {
 					ctx = config.EnableAlphaAPIFields(ctx)
 				}
 				ts.SetDefaults(ctx)
-				ctx = config.SkipValidationDueToPropagatedParametersAndWorkspaces(ctx, true)
 				err := ts.Validate(ctx)
 
 				if tt.requiredVersion != version && err == nil {
diff --git a/pkg/apis/pipeline/v1beta1/taskrun_validation.go b/pkg/apis/pipeline/v1beta1/taskrun_validation.go
index 48bb10c0488..300fd4aa930 100644
--- a/pkg/apis/pipeline/v1beta1/taskrun_validation.go
+++ b/pkg/apis/pipeline/v1beta1/taskrun_validation.go
@@ -62,8 +62,6 @@ func (ts *TaskRunSpec) Validate(ctx context.Context) (errs *apis.FieldError) {
 	}
 	// Validate TaskSpec if it's present.
 	if ts.TaskSpec != nil {
-		// skip validation of parameter and workspaces variables since we validate them via taskrunspec below.
-		ctx = config.SkipValidationDueToPropagatedParametersAndWorkspaces(ctx, true)
 		errs = errs.Also(ts.TaskSpec.Validate(ctx).ViaField("taskSpec"))
 	}
 
@@ -144,7 +142,7 @@ func (ts *TaskRunSpec) validateInlineParameters(ctx context.Context) (errs *apis
 	if ts.TaskSpec != nil && ts.TaskSpec.Steps != nil {
 		errs = errs.Also(ValidateParameterTypes(ctx, paramSpec))
 		errs = errs.Also(ValidateParameterVariables(ctx, ts.TaskSpec.Steps, paramSpec))
-		errs = errs.Also(validateUsageOfDeclaredParameters(ctx, ts.TaskSpec.Steps, paramSpec))
+		errs = errs.Also(ValidateUsageOfDeclaredParameters(ctx, ts.TaskSpec.Steps, paramSpec))
 	}
 	return errs
 }
diff --git a/pkg/reconciler/pipelinerun/pipelinerun.go b/pkg/reconciler/pipelinerun/pipelinerun.go
index 30e4125b9a8..83cb4649fae 100644
--- a/pkg/reconciler/pipelinerun/pipelinerun.go
+++ b/pkg/reconciler/pipelinerun/pipelinerun.go
@@ -442,9 +442,6 @@ func (c *Reconciler) reconcile(ctx context.Context, pr *v1beta1.PipelineRun, get
 		return controller.NewPermanentError(err)
 	}
 
-	// Because of parameter propagation, we skip validating it inside the pipelineSpec since it may
-	// not have the full list of defined parameters
-	ctx = config.SkipValidationDueToPropagatedParametersAndWorkspaces(ctx, true)
 	if err := pipelineSpec.Validate(ctx); err != nil {
 		// This Run has failed, so we need to mark it as failed and stop reconciling it
 		pr.Status.MarkFailed(ReasonFailedValidation,
diff --git a/pkg/reconciler/taskrun/taskrun.go b/pkg/reconciler/taskrun/taskrun.go
index bbcf5436553..6aa7283ab4b 100644
--- a/pkg/reconciler/taskrun/taskrun.go
+++ b/pkg/reconciler/taskrun/taskrun.go
@@ -106,11 +106,6 @@ func (c *Reconciler) ReconcileKind(ctx context.Context, tr *v1beta1.TaskRun) pkg
 	defer span.End()
 
 	span.SetAttributes(attribute.String("taskrun", tr.Name), attribute.String("namespace", tr.Namespace))
-
-	// By this time, params and workspaces should not be propagated for embedded tasks so we cannot
-	// validate that all parameter variables and workspaces used in the TaskSpec are declared by the Task.
-	ctx = config.SkipValidationDueToPropagatedParametersAndWorkspaces(ctx, true)
-
 	// Read the initial condition
 	before := tr.Status.GetCondition(apis.ConditionSucceeded)
 
@@ -694,7 +689,10 @@ func (c *Reconciler) createPod(ctx context.Context, ts *v1beta1.TaskSpec, tr *v1
 
 	// By this time, params and workspaces should be propagated down so we can
 	// validate that all parameter variables and workspaces used in the TaskSpec are declared by the Task.
-	ctx = config.SkipValidationDueToPropagatedParametersAndWorkspaces(ctx, false)
+	if validateErr := v1beta1.ValidateUsageOfDeclaredParameters(ctx, ts.Steps, ts.Params); validateErr != nil {
+		logger.Errorf("Failed to create a pod for taskrun: %s due to task validation error %v", tr.Name, validateErr)
+		return nil, validateErr
+	}
 	if validateErr := ts.Validate(ctx); validateErr != nil {
 		logger.Errorf("Failed to create a pod for taskrun: %s due to task validation error %v", tr.Name, validateErr)
 		return nil, validateErr