diff --git a/CHANGELOG.md b/CHANGELOG.md
index ca488a710..a6be5327c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,18 @@
 
 All notable changes to this project are documented in this file.
 
+## 0.30.1
+
+**Release date:** 2022-10-10
+
+This prerelease enables the use of container-level SAS tokens when using `Bucket` objects
+to access Azure Storage. The Azure SDK error message has also been enriched to hint Flux
+users the potential reasons in case of failure.
+
+Improvements:
+- List objects when checking if bucket exists to allow use of container-level SAS token
+  [#906](https://github.com/fluxcd/source-controller/pull/906)
+
 ## 0.30.0
 
 **Release date:** 2022-09-29
diff --git a/api/v1beta2/bucket_types.go b/api/v1beta2/bucket_types.go
index e0f353676..749c4eb0a 100644
--- a/api/v1beta2/bucket_types.go
+++ b/api/v1beta2/bucket_types.go
@@ -128,6 +128,11 @@ type BucketStatus struct {
 	// +optional
 	Artifact *Artifact `json:"artifact,omitempty"`
 
+	// ObservedIgnore is the observed exclusion patterns used for constructing
+	// the source artifact.
+	// +optional
+	ObservedIgnore *string `json:"observedIgnore,omitempty"`
+
 	meta.ReconcileRequestStatus `json:",inline"`
 }
 
diff --git a/api/v1beta2/gitrepository_types.go b/api/v1beta2/gitrepository_types.go
index 0f6a0a23a..e85127d6c 100644
--- a/api/v1beta2/gitrepository_types.go
+++ b/api/v1beta2/gitrepository_types.go
@@ -224,9 +224,27 @@ type GitRepositoryStatus struct {
 	// be used to determine if the content of the included repository has
 	// changed.
 	// It has the format of `<algo>:<checksum>`, for example: `sha256:<checksum>`.
+	//
+	// Deprecated: Replaced with explicit fields for observed artifact content
+	// config in the status.
 	// +optional
 	ContentConfigChecksum string `json:"contentConfigChecksum,omitempty"`
 
+	// ObservedIgnore is the observed exclusion patterns used for constructing
+	// the source artifact.
+	// +optional
+	ObservedIgnore *string `json:"observedIgnore,omitempty"`
+
+	// ObservedRecurseSubmodules is the observed resource submodules
+	// configuration used to produce the current Artifact.
+	// +optional
+	ObservedRecurseSubmodules bool `json:"observedRecurseSubmodules,omitempty"`
+
+	// ObservedInclude is the observed list of GitRepository resources used to
+	// to produce the current Artifact.
+	// +optional
+	ObservedInclude []GitRepositoryInclude `json:"observedInclude,omitempty"`
+
 	meta.ReconcileRequestStatus `json:",inline"`
 }
 
diff --git a/api/v1beta2/ocirepository_types.go b/api/v1beta2/ocirepository_types.go
index 91ca7f859..9f40f910c 100644
--- a/api/v1beta2/ocirepository_types.go
+++ b/api/v1beta2/ocirepository_types.go
@@ -211,9 +211,22 @@ type OCIRepositoryStatus struct {
 	// be used to determine if the content configuration has changed and the
 	// artifact needs to be rebuilt.
 	// It has the format of `<algo>:<checksum>`, for example: `sha256:<checksum>`.
+	//
+	// Deprecated: Replaced with explicit fields for observed artifact content
+	// config in the status.
 	// +optional
 	ContentConfigChecksum string `json:"contentConfigChecksum,omitempty"`
 
+	// ObservedIgnore is the observed exclusion patterns used for constructing
+	// the source artifact.
+	// +optional
+	ObservedIgnore *string `json:"observedIgnore,omitempty"`
+
+	// ObservedLayerSelector is the observed layer selector used for constructing
+	// the source artifact.
+	// +optional
+	ObservedLayerSelector *OCILayerSelector `json:"observedLayerSelector,omitempty"`
+
 	meta.ReconcileRequestStatus `json:",inline"`
 }
 
diff --git a/api/v1beta2/zz_generated.deepcopy.go b/api/v1beta2/zz_generated.deepcopy.go
index b759c3791..106a042c9 100644
--- a/api/v1beta2/zz_generated.deepcopy.go
+++ b/api/v1beta2/zz_generated.deepcopy.go
@@ -166,6 +166,11 @@ func (in *BucketStatus) DeepCopyInto(out *BucketStatus) {
 		*out = new(Artifact)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.ObservedIgnore != nil {
+		in, out := &in.ObservedIgnore, &out.ObservedIgnore
+		*out = new(string)
+		**out = **in
+	}
 	out.ReconcileRequestStatus = in.ReconcileRequestStatus
 }
 
@@ -346,6 +351,16 @@ func (in *GitRepositoryStatus) DeepCopyInto(out *GitRepositoryStatus) {
 			}
 		}
 	}
+	if in.ObservedIgnore != nil {
+		in, out := &in.ObservedIgnore, &out.ObservedIgnore
+		*out = new(string)
+		**out = **in
+	}
+	if in.ObservedInclude != nil {
+		in, out := &in.ObservedInclude, &out.ObservedInclude
+		*out = make([]GitRepositoryInclude, len(*in))
+		copy(*out, *in)
+	}
 	out.ReconcileRequestStatus = in.ReconcileRequestStatus
 }
 
@@ -777,6 +792,16 @@ func (in *OCIRepositoryStatus) DeepCopyInto(out *OCIRepositoryStatus) {
 		*out = new(Artifact)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.ObservedIgnore != nil {
+		in, out := &in.ObservedIgnore, &out.ObservedIgnore
+		*out = new(string)
+		**out = **in
+	}
+	if in.ObservedLayerSelector != nil {
+		in, out := &in.ObservedLayerSelector, &out.ObservedLayerSelector
+		*out = new(OCILayerSelector)
+		**out = **in
+	}
 	out.ReconcileRequestStatus = in.ReconcileRequestStatus
 }
 
diff --git a/config/crd/bases/source.toolkit.fluxcd.io_buckets.yaml b/config/crd/bases/source.toolkit.fluxcd.io_buckets.yaml
index 2ea76752f..49c02e415 100644
--- a/config/crd/bases/source.toolkit.fluxcd.io_buckets.yaml
+++ b/config/crd/bases/source.toolkit.fluxcd.io_buckets.yaml
@@ -492,6 +492,10 @@ spec:
                   the Bucket object.
                 format: int64
                 type: integer
+              observedIgnore:
+                description: ObservedIgnore is the observed exclusion patterns used
+                  for constructing the source artifact.
+                type: string
               url:
                 description: URL is the dynamic fetch link for the latest Artifact.
                   It is provided on a "best effort" basis, and using the precise BucketStatus.Artifact
diff --git a/config/crd/bases/source.toolkit.fluxcd.io_gitrepositories.yaml b/config/crd/bases/source.toolkit.fluxcd.io_gitrepositories.yaml
index 9380f20c9..032cfe483 100644
--- a/config/crd/bases/source.toolkit.fluxcd.io_gitrepositories.yaml
+++ b/config/crd/bases/source.toolkit.fluxcd.io_gitrepositories.yaml
@@ -658,13 +658,14 @@ spec:
                   type: object
                 type: array
               contentConfigChecksum:
-                description: 'ContentConfigChecksum is a checksum of all the configurations
+                description: "ContentConfigChecksum is a checksum of all the configurations
                   related to the content of the source artifact: - .spec.ignore -
                   .spec.recurseSubmodules - .spec.included and the checksum of the
                   included artifacts observed in .status.observedGeneration version
                   of the object. This can be used to determine if the content of the
                   included repository has changed. It has the format of `<algo>:<checksum>`,
-                  for example: `sha256:<checksum>`.'
+                  for example: `sha256:<checksum>`. \n Deprecated: Replaced with explicit
+                  fields for observed artifact content config in the status."
                 type: string
               includedArtifacts:
                 description: IncludedArtifacts contains a list of the last successfully
@@ -723,6 +724,44 @@ spec:
                   the GitRepository object.
                 format: int64
                 type: integer
+              observedIgnore:
+                description: ObservedIgnore is the observed exclusion patterns used
+                  for constructing the source artifact.
+                type: string
+              observedInclude:
+                description: ObservedInclude is the observed list of GitRepository
+                  resources used to to produce the current Artifact.
+                items:
+                  description: GitRepositoryInclude specifies a local reference to
+                    a GitRepository which Artifact (sub-)contents must be included,
+                    and where they should be placed.
+                  properties:
+                    fromPath:
+                      description: FromPath specifies the path to copy contents from,
+                        defaults to the root of the Artifact.
+                      type: string
+                    repository:
+                      description: GitRepositoryRef specifies the GitRepository which
+                        Artifact contents must be included.
+                      properties:
+                        name:
+                          description: Name of the referent.
+                          type: string
+                      required:
+                      - name
+                      type: object
+                    toPath:
+                      description: ToPath specifies the path to copy contents to,
+                        defaults to the name of the GitRepositoryRef.
+                      type: string
+                  required:
+                  - repository
+                  type: object
+                type: array
+              observedRecurseSubmodules:
+                description: ObservedRecurseSubmodules is the observed resource submodules
+                  configuration used to produce the current Artifact.
+                type: boolean
               url:
                 description: URL is the dynamic fetch link for the latest Artifact.
                   It is provided on a "best effort" basis, and using the precise GitRepositoryStatus.Artifact
diff --git a/config/crd/bases/source.toolkit.fluxcd.io_ocirepositories.yaml b/config/crd/bases/source.toolkit.fluxcd.io_ocirepositories.yaml
index 2d236ec99..d40c11861 100644
--- a/config/crd/bases/source.toolkit.fluxcd.io_ocirepositories.yaml
+++ b/config/crd/bases/source.toolkit.fluxcd.io_ocirepositories.yaml
@@ -301,12 +301,14 @@ spec:
                   type: object
                 type: array
               contentConfigChecksum:
-                description: 'ContentConfigChecksum is a checksum of all the configurations
+                description: "ContentConfigChecksum is a checksum of all the configurations
                   related to the content of the source artifact: - .spec.ignore -
                   .spec.layerSelector observed in .status.observedGeneration version
                   of the object. This can be used to determine if the content configuration
                   has changed and the artifact needs to be rebuilt. It has the format
-                  of `<algo>:<checksum>`, for example: `sha256:<checksum>`.'
+                  of `<algo>:<checksum>`, for example: `sha256:<checksum>`. \n Deprecated:
+                  Replaced with explicit fields for observed artifact content config
+                  in the status."
                 type: string
               lastHandledReconcileAt:
                 description: LastHandledReconcileAt holds the value of the most recent
@@ -317,6 +319,29 @@ spec:
                 description: ObservedGeneration is the last observed generation.
                 format: int64
                 type: integer
+              observedIgnore:
+                description: ObservedIgnore is the observed exclusion patterns used
+                  for constructing the source artifact.
+                type: string
+              observedLayerSelector:
+                description: ObservedLayerSelector is the observed layer selector
+                  used for constructing the source artifact.
+                properties:
+                  mediaType:
+                    description: MediaType specifies the OCI media type of the layer
+                      which should be extracted from the OCI Artifact. The first layer
+                      matching this type is selected.
+                    type: string
+                  operation:
+                    description: Operation specifies how the selected layer should
+                      be processed. By default, the layer compressed content is extracted
+                      to storage. When the operation is set to 'copy', the layer compressed
+                      content is persisted to storage as it is.
+                    enum:
+                    - extract
+                    - copy
+                    type: string
+                type: object
               url:
                 description: URL is the download link for the artifact output of the
                   last OCI Repository sync.
diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml
index 665c9d979..48b83f648 100644
--- a/config/manager/kustomization.yaml
+++ b/config/manager/kustomization.yaml
@@ -6,4 +6,4 @@ resources:
 images:
 - name: fluxcd/source-controller
   newName: fluxcd/source-controller
-  newTag: v0.30.0
+  newTag: v0.30.1
diff --git a/controllers/bucket_controller.go b/controllers/bucket_controller.go
index 98076889c..f2608bf40 100644
--- a/controllers/bucket_controller.go
+++ b/controllers/bucket_controller.go
@@ -628,6 +628,7 @@ func (r *BucketReconciler) reconcileArtifact(ctx context.Context, obj *sourcev1.
 
 	// Record it on the object
 	obj.Status.Artifact = artifact.DeepCopy()
+	obj.Status.ObservedIgnore = obj.Spec.Ignore
 
 	// Update symlink on a "best effort" basis
 	url, err := r.Storage.Symlink(artifact, "latest.tar.gz")
diff --git a/controllers/bucket_controller_test.go b/controllers/bucket_controller_test.go
index 37cc33d91..b0ec4a531 100644
--- a/controllers/bucket_controller_test.go
+++ b/controllers/bucket_controller_test.go
@@ -28,10 +28,6 @@ import (
 	"testing"
 	"time"
 
-	"github.com/darkowlzz/controller-check/status"
-	"github.com/fluxcd/pkg/apis/meta"
-	"github.com/fluxcd/pkg/runtime/conditions"
-	"github.com/fluxcd/pkg/runtime/patch"
 	. "github.com/onsi/gomega"
 	corev1 "k8s.io/api/core/v1"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
@@ -42,6 +38,11 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/client/fake"
 	fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
 
+	"github.com/fluxcd/pkg/apis/meta"
+	"github.com/fluxcd/pkg/runtime/conditions"
+	conditionscheck "github.com/fluxcd/pkg/runtime/conditions/check"
+	"github.com/fluxcd/pkg/runtime/patch"
+
 	sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
 	gcsmock "github.com/fluxcd/source-controller/internal/mock/gcs"
 	s3mock "github.com/fluxcd/source-controller/internal/mock/s3"
@@ -127,8 +128,8 @@ func TestBucketReconciler_Reconcile(t *testing.T) {
 	}, timeout).Should(BeTrue())
 
 	// Check if the object status is valid.
-	condns := &status.Conditions{NegativePolarity: bucketReadyCondition.NegativePolarity}
-	checker := status.NewChecker(testEnv.Client, condns)
+	condns := &conditionscheck.Conditions{NegativePolarity: bucketReadyCondition.NegativePolarity}
+	checker := conditionscheck.NewChecker(testEnv.Client, condns)
 	checker.CheckErr(ctx, obj)
 
 	// kstatus client conformance check.
diff --git a/controllers/gitrepository_controller.go b/controllers/gitrepository_controller.go
index a0a5cee9f..8ea55aae1 100644
--- a/controllers/gitrepository_controller.go
+++ b/controllers/gitrepository_controller.go
@@ -18,12 +18,10 @@ package controllers
 
 import (
 	"context"
-	"crypto/sha256"
 	"errors"
 	"fmt"
 	"os"
 	"path/filepath"
-	"strconv"
 	"strings"
 	"time"
 
@@ -33,6 +31,7 @@ import (
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/types"
 	kuberecorder "k8s.io/client-go/tools/record"
+	"k8s.io/utils/pointer"
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/builder"
 	"sigs.k8s.io/controller-runtime/pkg/client"
@@ -507,8 +506,8 @@ func (r *GitRepositoryReconciler) reconcileSource(ctx context.Context,
 	// If it's a partial commit obtained from an existing artifact, check if the
 	// reconciliation can be skipped if other configurations have not changed.
 	if !git.IsConcreteCommit(*commit) {
-		// Calculate content configuration checksum.
-		if r.calculateContentConfigChecksum(obj, includes) == obj.Status.ContentConfigChecksum {
+		// Check if the content config contributing to the artifact has changed.
+		if !gitContentConfigChanged(obj, includes) {
 			ge := serror.NewGeneric(
 				fmt.Errorf("no changes since last reconcilation: observed revision '%s'",
 					commit.String()), sourcev1.GitOperationSucceedReason,
@@ -559,27 +558,24 @@ func (r *GitRepositoryReconciler) reconcileSource(ctx context.Context,
 //
 // The inspection of the given data to the object is differed, ensuring any
 // stale observations like v1beta2.ArtifactOutdatedCondition are removed.
-// If the given Artifact and/or artifactSet (includes) and the content config
-// checksum do not differ from the object's current, it returns early.
+// If the given Artifact and/or artifactSet (includes) and observed artifact
+// content config do not differ from the object's current, it returns early.
 // Source ignore patterns are loaded, and the given directory is archived while
 // taking these patterns into account.
-// On a successful archive, the Artifact, Includes and new content config
-// checksum in the Status of the object are set, and the symlink in the Storage
-// is updated to its path.
+// On a successful archive, the Artifact, Includes, observed ignore, recurse
+// submodules and observed include in the Status of the object are set, and the
+// symlink in the Storage is updated to its path.
 func (r *GitRepositoryReconciler) reconcileArtifact(ctx context.Context,
 	obj *sourcev1.GitRepository, commit *git.Commit, includes *artifactSet, dir string) (sreconcile.Result, error) {
 
 	// Create potential new artifact with current available metadata
 	artifact := r.Storage.NewArtifactFor(obj.Kind, obj.GetObjectMeta(), commit.String(), fmt.Sprintf("%s.tar.gz", commit.Hash.String()))
 
-	// Calculate the content config checksum.
-	ccc := r.calculateContentConfigChecksum(obj, includes)
-
 	// Set the ArtifactInStorageCondition if there's no drift.
 	defer func() {
 		if obj.GetArtifact().HasRevision(artifact.Revision) &&
 			!includes.Diff(obj.Status.IncludedArtifacts) &&
-			obj.Status.ContentConfigChecksum == ccc {
+			!gitContentConfigChanged(obj, includes) {
 			conditions.Delete(obj, sourcev1.ArtifactOutdatedCondition)
 			conditions.MarkTrue(obj, sourcev1.ArtifactInStorageCondition, meta.SucceededReason,
 				"stored artifact for revision '%s'", artifact.Revision)
@@ -589,7 +585,7 @@ func (r *GitRepositoryReconciler) reconcileArtifact(ctx context.Context,
 	// The artifact is up-to-date
 	if obj.GetArtifact().HasRevision(artifact.Revision) &&
 		!includes.Diff(obj.Status.IncludedArtifacts) &&
-		obj.Status.ContentConfigChecksum == ccc {
+		!gitContentConfigChanged(obj, includes) {
 		r.eventLogf(ctx, obj, events.EventTypeTrace, sourcev1.ArtifactUpToDateReason, "artifact up-to-date with remote revision: '%s'", artifact.Revision)
 		return sreconcile.ResultSuccess, nil
 	}
@@ -652,10 +648,13 @@ func (r *GitRepositoryReconciler) reconcileArtifact(ctx context.Context,
 		return sreconcile.ResultEmpty, e
 	}
 
-	// Record it on the object
+	// Record the observations on the object.
 	obj.Status.Artifact = artifact.DeepCopy()
 	obj.Status.IncludedArtifacts = *includes
-	obj.Status.ContentConfigChecksum = ccc
+	obj.Status.ContentConfigChecksum = "" // To be removed in the next API version.
+	obj.Status.ObservedIgnore = obj.Spec.Ignore
+	obj.Status.ObservedRecurseSubmodules = obj.Spec.RecurseSubmodules
+	obj.Status.ObservedInclude = obj.Spec.Include
 
 	// Update symlink on a "best effort" basis
 	url, err := r.Storage.Symlink(artifact, "latest.tar.gz")
@@ -825,39 +824,6 @@ func (r *GitRepositoryReconciler) fetchIncludes(ctx context.Context, obj *source
 	return &artifacts, nil
 }
 
-// calculateContentConfigChecksum calculates a checksum of all the
-// configurations that result in a change in the source artifact. It can be used
-// to decide if further reconciliation is needed when an artifact already exists
-// for a set of configurations.
-func (r *GitRepositoryReconciler) calculateContentConfigChecksum(obj *sourcev1.GitRepository, includes *artifactSet) string {
-	c := []byte{}
-	// Consider the ignore rules and recurse submodules.
-	if obj.Spec.Ignore != nil {
-		c = append(c, []byte(*obj.Spec.Ignore)...)
-	}
-	c = append(c, []byte(strconv.FormatBool(obj.Spec.RecurseSubmodules))...)
-
-	// Consider the included repository attributes.
-	for _, incl := range obj.Spec.Include {
-		c = append(c, []byte(incl.GitRepositoryRef.Name+incl.FromPath+incl.ToPath)...)
-	}
-
-	// Consider the checksum and revision of all the included remote artifact.
-	// This ensures that if the included repos get updated, this checksum changes.
-	// NOTE: The content of an artifact may change at the same revision if the
-	// ignore rules change. Hence, consider both checksum and revision to
-	// capture changes in artifact checksum as well.
-	// TODO: Fix artifactSet.Diff() to consider checksum as well.
-	if includes != nil {
-		for _, incl := range *includes {
-			c = append(c, []byte(incl.Checksum)...)
-			c = append(c, []byte(incl.Revision)...)
-		}
-	}
-
-	return fmt.Sprintf("sha256:%x", sha256.Sum256(c))
-}
-
 // verifyCommitSignature verifies the signature of the given Git commit, if a
 // verification mode is specified on the object.
 // If the signature can not be verified or the verification fails, it records
@@ -978,3 +944,64 @@ func (r *GitRepositoryReconciler) eventLogf(ctx context.Context, obj runtime.Obj
 	}
 	r.Eventf(obj, eventType, reason, msg)
 }
+
+// gitContentConfigChanged evaluates the current spec with the observations of
+// the artifact in the status to determine if artifact content configuration has
+// changed and requires rebuilding the artifact.
+func gitContentConfigChanged(obj *sourcev1.GitRepository, includes *artifactSet) bool {
+	if !pointer.StringEqual(obj.Spec.Ignore, obj.Status.ObservedIgnore) {
+		return true
+	}
+	if obj.Spec.RecurseSubmodules != obj.Status.ObservedRecurseSubmodules {
+		return true
+	}
+	if len(obj.Spec.Include) != len(obj.Status.ObservedInclude) {
+		return true
+	}
+
+	// Convert artifactSet to index addressable artifacts and ensure that it and
+	// the included artifacts include all the include from the spec.
+	artifacts := []*sourcev1.Artifact(*includes)
+	if len(obj.Spec.Include) != len(artifacts) {
+		return true
+	}
+	if len(obj.Spec.Include) != len(obj.Status.IncludedArtifacts) {
+		return true
+	}
+
+	// The order of spec.include, status.IncludeArtifacts and
+	// status.observedInclude are the same. Compare the values by index.
+	for index, incl := range obj.Spec.Include {
+		observedIncl := obj.Status.ObservedInclude[index]
+		observedInclArtifact := obj.Status.IncludedArtifacts[index]
+		currentIncl := artifacts[index]
+
+		// Check if the include are the same in spec and status.
+		if !gitRepositoryIncludeEqual(incl, observedIncl) {
+			return true
+		}
+
+		// Check if the included repositories are still the same.
+		if observedInclArtifact.Revision != currentIncl.Revision {
+			return true
+		}
+		if observedInclArtifact.Checksum != currentIncl.Checksum {
+			return true
+		}
+	}
+	return false
+}
+
+// Returns true if both GitRepositoryIncludes are equal.
+func gitRepositoryIncludeEqual(a, b sourcev1.GitRepositoryInclude) bool {
+	if a.GitRepositoryRef != b.GitRepositoryRef {
+		return false
+	}
+	if a.FromPath != b.FromPath {
+		return false
+	}
+	if a.ToPath != b.ToPath {
+		return false
+	}
+	return true
+}
diff --git a/controllers/gitrepository_controller_test.go b/controllers/gitrepository_controller_test.go
index bfb857df0..52b131bcf 100644
--- a/controllers/gitrepository_controller_test.go
+++ b/controllers/gitrepository_controller_test.go
@@ -28,13 +28,6 @@ import (
 	"testing"
 	"time"
 
-	"github.com/darkowlzz/controller-check/status"
-	"github.com/fluxcd/pkg/apis/meta"
-	"github.com/fluxcd/pkg/gittestserver"
-	"github.com/fluxcd/pkg/runtime/conditions"
-	"github.com/fluxcd/pkg/runtime/patch"
-	"github.com/fluxcd/pkg/ssh"
-	"github.com/fluxcd/pkg/testserver"
 	"github.com/go-git/go-billy/v5/memfs"
 	gogit "github.com/go-git/go-git/v5"
 	"github.com/go-git/go-git/v5/config"
@@ -56,6 +49,14 @@ import (
 	fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
 	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
 
+	"github.com/fluxcd/pkg/apis/meta"
+	"github.com/fluxcd/pkg/gittestserver"
+	"github.com/fluxcd/pkg/runtime/conditions"
+	conditionscheck "github.com/fluxcd/pkg/runtime/conditions/check"
+	"github.com/fluxcd/pkg/runtime/patch"
+	"github.com/fluxcd/pkg/ssh"
+	"github.com/fluxcd/pkg/testserver"
+
 	sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
 	serror "github.com/fluxcd/source-controller/internal/error"
 	"github.com/fluxcd/source-controller/internal/features"
@@ -143,7 +144,6 @@ Oomb3gD/TRf/nAdVED+k81GdLzciYdUGtI71/qI47G0nMBluLRE=
 =/4e+
 -----END PGP PUBLIC KEY BLOCK-----
 `
-	emptyContentConfigChecksum = "sha256:fcbcf165908dd18a9e49f7ff27810176db8e9f63b4352213741664245224f8aa"
 )
 
 var (
@@ -204,8 +204,8 @@ func TestGitRepositoryReconciler_Reconcile(t *testing.T) {
 	}, timeout).Should(BeTrue())
 
 	// Check if the object status is valid.
-	condns := &status.Conditions{NegativePolarity: gitRepositoryReadyCondition.NegativePolarity}
-	checker := status.NewChecker(testEnv.Client, condns)
+	condns := &conditionscheck.Conditions{NegativePolarity: gitRepositoryReadyCondition.NegativePolarity}
+	checker := conditionscheck.NewChecker(testEnv.Client, condns)
 	checker.CheckErr(ctx, obj)
 
 	// kstatus client conformance check.
@@ -685,8 +685,6 @@ func TestGitRepositoryReconciler_reconcileSource_checkoutStrategy(t *testing.T)
 						Revision: "staging/" + latestRev,
 						Path:     randStringRunes(10),
 					},
-					// Checksum with all the relevant fields unset.
-					ContentConfigChecksum: emptyContentConfigChecksum,
 				}
 				conditions.MarkTrue(obj, sourcev1.ArtifactInStorageCondition, meta.SucceededReason, "foo")
 			},
@@ -709,8 +707,6 @@ func TestGitRepositoryReconciler_reconcileSource_checkoutStrategy(t *testing.T)
 						Revision: "staging/" + latestRev,
 						Path:     randStringRunes(10),
 					},
-					// Checksum with all the relevant fields unset.
-					ContentConfigChecksum: emptyContentConfigChecksum,
 				}
 				conditions.MarkTrue(obj, sourcev1.ArtifactInStorageCondition, meta.SucceededReason, "foo")
 			},
@@ -835,6 +831,9 @@ func TestGitRepositoryReconciler_reconcileArtifact(t *testing.T) {
 			includes: artifactSet{&sourcev1.Artifact{Revision: "main/revision"}},
 			beforeFunc: func(obj *sourcev1.GitRepository) {
 				obj.Spec.Interval = metav1.Duration{Duration: interval}
+				obj.Spec.Include = []sourcev1.GitRepositoryInclude{
+					{GitRepositoryRef: meta.LocalObjectReference{Name: "foo"}},
+				}
 			},
 			afterFunc: func(t *WithT, obj *sourcev1.GitRepository) {
 				t.Expect(obj.GetArtifact()).ToNot(BeNil())
@@ -850,12 +849,15 @@ func TestGitRepositoryReconciler_reconcileArtifact(t *testing.T) {
 		{
 			name:     "Up-to-date artifact should not update status",
 			dir:      "testdata/git/repository",
-			includes: artifactSet{&sourcev1.Artifact{Revision: "main/revision"}},
+			includes: artifactSet{&sourcev1.Artifact{Revision: "main/revision", Checksum: "some-checksum"}},
 			beforeFunc: func(obj *sourcev1.GitRepository) {
 				obj.Spec.Interval = metav1.Duration{Duration: interval}
+				obj.Spec.Include = []sourcev1.GitRepositoryInclude{
+					{GitRepositoryRef: meta.LocalObjectReference{Name: "foo"}},
+				}
 				obj.Status.Artifact = &sourcev1.Artifact{Revision: "main/revision"}
 				obj.Status.IncludedArtifacts = []*sourcev1.Artifact{{Revision: "main/revision", Checksum: "some-checksum"}}
-				obj.Status.ContentConfigChecksum = "sha256:f825d11a1c5987e033d2cb36449a3b0435a6abc9b2bfdbcdcc7c49bf40e9285d"
+				obj.Status.ObservedInclude = obj.Spec.Include
 			},
 			afterFunc: func(t *WithT, obj *sourcev1.GitRepository) {
 				t.Expect(obj.Status.URL).To(BeEmpty())
@@ -2145,53 +2147,6 @@ func TestGitRepositoryReconciler_fetchIncludes(t *testing.T) {
 	}
 }
 
-func TestGitRepositoryReconciler_calculateContentConfigChecksum(t *testing.T) {
-	g := NewWithT(t)
-	obj := &sourcev1.GitRepository{}
-	r := &GitRepositoryReconciler{}
-
-	emptyChecksum := r.calculateContentConfigChecksum(obj, nil)
-	g.Expect(emptyChecksum).To(Equal(emptyContentConfigChecksum))
-
-	// Ignore modified.
-	obj.Spec.Ignore = pointer.String("some-rule")
-	ignoreModChecksum := r.calculateContentConfigChecksum(obj, nil)
-	g.Expect(emptyChecksum).ToNot(Equal(ignoreModChecksum))
-
-	// Recurse submodules modified.
-	obj.Spec.RecurseSubmodules = true
-	submodModChecksum := r.calculateContentConfigChecksum(obj, nil)
-	g.Expect(ignoreModChecksum).ToNot(Equal(submodModChecksum))
-
-	// Include modified.
-	obj.Spec.Include = []sourcev1.GitRepositoryInclude{
-		{
-			GitRepositoryRef: meta.LocalObjectReference{Name: "foo"},
-			FromPath:         "aaa",
-			ToPath:           "bbb",
-		},
-	}
-	artifacts := &artifactSet{
-		&sourcev1.Artifact{Revision: "some-revision-1", Checksum: "some-checksum-1"},
-	}
-	includeModChecksum := r.calculateContentConfigChecksum(obj, artifacts)
-	g.Expect(submodModChecksum).ToNot(Equal(includeModChecksum))
-
-	// Artifact modified revision.
-	artifacts = &artifactSet{
-		&sourcev1.Artifact{Revision: "some-revision-2", Checksum: "some-checksum-1"},
-	}
-	artifactModChecksum := r.calculateContentConfigChecksum(obj, artifacts)
-	g.Expect(includeModChecksum).ToNot(Equal(artifactModChecksum))
-
-	// Artifact modified checksum.
-	artifacts = &artifactSet{
-		&sourcev1.Artifact{Revision: "some-revision-2", Checksum: "some-checksum-2"},
-	}
-	artifactCsumModChecksum := r.calculateContentConfigChecksum(obj, artifacts)
-	g.Expect(artifactModChecksum).ToNot(Equal(artifactCsumModChecksum))
-}
-
 func resetChmod(path string, dirMode os.FileMode, fileMode os.FileMode) error {
 	err := filepath.Walk(path,
 		func(path string, info os.FileInfo, err error) error {
@@ -2212,3 +2167,371 @@ func resetChmod(path string, dirMode os.FileMode, fileMode os.FileMode) error {
 
 	return nil
 }
+
+func TestGitRepositoryIncludeEqual(t *testing.T) {
+	tests := []struct {
+		name string
+		a    sourcev1.GitRepositoryInclude
+		b    sourcev1.GitRepositoryInclude
+		want bool
+	}{
+		{
+			name: "empty",
+			want: true,
+		},
+		{
+			name: "different refs",
+			a: sourcev1.GitRepositoryInclude{
+				GitRepositoryRef: meta.LocalObjectReference{Name: "foo"},
+			},
+			b: sourcev1.GitRepositoryInclude{
+				GitRepositoryRef: meta.LocalObjectReference{Name: "bar"},
+			},
+			want: false,
+		},
+		{
+			name: "same refs",
+			a: sourcev1.GitRepositoryInclude{
+				GitRepositoryRef: meta.LocalObjectReference{Name: "foo"},
+			},
+			b: sourcev1.GitRepositoryInclude{
+				GitRepositoryRef: meta.LocalObjectReference{Name: "foo"},
+			},
+			want: true,
+		},
+		{
+			name: "different from paths",
+			a:    sourcev1.GitRepositoryInclude{FromPath: "foo"},
+			b:    sourcev1.GitRepositoryInclude{FromPath: "bar"},
+			want: false,
+		},
+		{
+			name: "same from paths",
+			a:    sourcev1.GitRepositoryInclude{FromPath: "foo"},
+			b:    sourcev1.GitRepositoryInclude{FromPath: "foo"},
+			want: true,
+		},
+		{
+			name: "different to paths",
+			a:    sourcev1.GitRepositoryInclude{ToPath: "foo"},
+			b:    sourcev1.GitRepositoryInclude{ToPath: "bar"},
+			want: false,
+		},
+		{
+			name: "same to paths",
+			a:    sourcev1.GitRepositoryInclude{ToPath: "foo"},
+			b:    sourcev1.GitRepositoryInclude{ToPath: "foo"},
+			want: true,
+		},
+		{
+			name: "same all",
+			a: sourcev1.GitRepositoryInclude{
+				GitRepositoryRef: meta.LocalObjectReference{Name: "foo-ref"},
+				FromPath:         "foo-path",
+				ToPath:           "bar-path",
+			},
+			b: sourcev1.GitRepositoryInclude{
+				GitRepositoryRef: meta.LocalObjectReference{Name: "foo-ref"},
+				FromPath:         "foo-path",
+				ToPath:           "bar-path",
+			},
+			want: true,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			g := NewWithT(t)
+
+			g.Expect(gitRepositoryIncludeEqual(tt.a, tt.b)).To(Equal(tt.want))
+		})
+	}
+}
+
+func TestGitContentConfigChanged(t *testing.T) {
+	tests := []struct {
+		name      string
+		obj       sourcev1.GitRepository
+		artifacts []*sourcev1.Artifact
+		want      bool
+	}{
+		{
+			name: "no content config",
+			want: false,
+		},
+		{
+			name: "unobserved ignore",
+			obj: sourcev1.GitRepository{
+				Spec: sourcev1.GitRepositorySpec{Ignore: pointer.String("foo")},
+			},
+			want: true,
+		},
+		{
+			name: "observed ignore",
+			obj: sourcev1.GitRepository{
+				Spec:   sourcev1.GitRepositorySpec{Ignore: pointer.String("foo")},
+				Status: sourcev1.GitRepositoryStatus{ObservedIgnore: pointer.String("foo")},
+			},
+			want: false,
+		},
+		{
+			name: "unobserved recurse submodules",
+			obj: sourcev1.GitRepository{
+				Spec: sourcev1.GitRepositorySpec{RecurseSubmodules: true},
+			},
+			want: true,
+		},
+		{
+			name: "observed recurse submodules",
+			obj: sourcev1.GitRepository{
+				Spec:   sourcev1.GitRepositorySpec{RecurseSubmodules: true},
+				Status: sourcev1.GitRepositoryStatus{ObservedRecurseSubmodules: true},
+			},
+			want: false,
+		},
+		{
+			name: "unobserved include",
+			obj: sourcev1.GitRepository{
+				Spec: sourcev1.GitRepositorySpec{
+					Include: []sourcev1.GitRepositoryInclude{
+						{GitRepositoryRef: meta.LocalObjectReference{Name: "foo"}, FromPath: "bar", ToPath: "baz"},
+					},
+				},
+			},
+			want: true,
+		},
+		{
+			name: "observed include",
+			obj: sourcev1.GitRepository{
+				Spec: sourcev1.GitRepositorySpec{
+					Include: []sourcev1.GitRepositoryInclude{
+						{
+							GitRepositoryRef: meta.LocalObjectReference{Name: "foo"},
+							FromPath:         "bar",
+							ToPath:           "baz",
+						},
+					},
+				},
+				Status: sourcev1.GitRepositoryStatus{
+					ObservedInclude: []sourcev1.GitRepositoryInclude{
+						{
+							GitRepositoryRef: meta.LocalObjectReference{Name: "foo"},
+							FromPath:         "bar",
+							ToPath:           "baz",
+						},
+					},
+					IncludedArtifacts: []*sourcev1.Artifact{{Revision: "aaa", Checksum: "bbb"}},
+				},
+			},
+			artifacts: []*sourcev1.Artifact{
+				{Revision: "aaa", Checksum: "bbb"},
+			},
+			want: false,
+		},
+		{
+			name: "observed include but different artifact revision",
+			obj: sourcev1.GitRepository{
+				Spec: sourcev1.GitRepositorySpec{
+					Include: []sourcev1.GitRepositoryInclude{
+						{
+							GitRepositoryRef: meta.LocalObjectReference{Name: "foo"},
+							FromPath:         "bar",
+							ToPath:           "baz",
+						},
+					},
+				},
+				Status: sourcev1.GitRepositoryStatus{
+					ObservedInclude: []sourcev1.GitRepositoryInclude{
+						{
+							GitRepositoryRef: meta.LocalObjectReference{Name: "foo"},
+							FromPath:         "bar",
+							ToPath:           "baz",
+						},
+					},
+					IncludedArtifacts: []*sourcev1.Artifact{{Revision: "aaa", Checksum: "bbb"}},
+				},
+			},
+			artifacts: []*sourcev1.Artifact{
+				{Revision: "ccc", Checksum: "bbb"},
+			},
+			want: true,
+		},
+		{
+			name: "observed include but different artifact checksum",
+			obj: sourcev1.GitRepository{
+				Spec: sourcev1.GitRepositorySpec{
+					Include: []sourcev1.GitRepositoryInclude{
+						{
+							GitRepositoryRef: meta.LocalObjectReference{Name: "foo"},
+							FromPath:         "bar",
+							ToPath:           "baz",
+						},
+					},
+				},
+				Status: sourcev1.GitRepositoryStatus{
+					ObservedInclude: []sourcev1.GitRepositoryInclude{
+						{
+							GitRepositoryRef: meta.LocalObjectReference{Name: "foo"},
+							FromPath:         "bar",
+							ToPath:           "baz",
+						},
+					},
+					IncludedArtifacts: []*sourcev1.Artifact{{Revision: "aaa", Checksum: "bbb"}},
+				},
+			},
+			artifacts: []*sourcev1.Artifact{
+				{Revision: "aaa", Checksum: "ddd"},
+			},
+			want: true,
+		},
+		{
+			name: "observed include but updated spec",
+			obj: sourcev1.GitRepository{
+				Spec: sourcev1.GitRepositorySpec{
+					Include: []sourcev1.GitRepositoryInclude{
+						{
+							GitRepositoryRef: meta.LocalObjectReference{Name: "foo2"},
+							FromPath:         "bar",
+							ToPath:           "baz",
+						},
+					},
+				},
+				Status: sourcev1.GitRepositoryStatus{
+					ObservedInclude: []sourcev1.GitRepositoryInclude{
+						{
+							GitRepositoryRef: meta.LocalObjectReference{Name: "foo"},
+							FromPath:         "bar",
+							ToPath:           "baz",
+						},
+					},
+					IncludedArtifacts: []*sourcev1.Artifact{{Revision: "aaa", Checksum: "bbb"}},
+				},
+			},
+			artifacts: []*sourcev1.Artifact{
+				{Revision: "aaa", Checksum: "bbb"},
+			},
+			want: true,
+		},
+		{
+			name: "different number of include and observed include",
+			obj: sourcev1.GitRepository{
+				Spec: sourcev1.GitRepositorySpec{
+					Include: []sourcev1.GitRepositoryInclude{
+						{
+							GitRepositoryRef: meta.LocalObjectReference{Name: "foo"},
+							FromPath:         "bar",
+							ToPath:           "baz",
+						},
+						{
+							GitRepositoryRef: meta.LocalObjectReference{Name: "foo2"},
+							FromPath:         "bar",
+							ToPath:           "baz",
+						},
+					},
+				},
+				Status: sourcev1.GitRepositoryStatus{
+					IncludedArtifacts: []*sourcev1.Artifact{
+						{Revision: "aaa", Checksum: "bbb"},
+						{Revision: "ccc", Checksum: "ccc"},
+					},
+				},
+			},
+			artifacts: []*sourcev1.Artifact{
+				{Revision: "aaa", Checksum: "bbb"},
+				{Revision: "ccc", Checksum: "ddd"},
+			},
+			want: true,
+		},
+		{
+			name: "different number of include and artifactset",
+			obj: sourcev1.GitRepository{
+				Spec: sourcev1.GitRepositorySpec{
+					Include: []sourcev1.GitRepositoryInclude{
+						{
+							GitRepositoryRef: meta.LocalObjectReference{Name: "foo"},
+							FromPath:         "bar",
+							ToPath:           "baz",
+						},
+						{
+							GitRepositoryRef: meta.LocalObjectReference{Name: "foo2"},
+							FromPath:         "bar",
+							ToPath:           "baz",
+						},
+					},
+				},
+				Status: sourcev1.GitRepositoryStatus{
+					ObservedInclude: []sourcev1.GitRepositoryInclude{
+						{
+							GitRepositoryRef: meta.LocalObjectReference{Name: "foo"},
+							FromPath:         "bar",
+							ToPath:           "baz",
+						},
+						{
+							GitRepositoryRef: meta.LocalObjectReference{Name: "foo2"},
+							FromPath:         "bar",
+							ToPath:           "baz",
+						},
+					},
+					IncludedArtifacts: []*sourcev1.Artifact{
+						{Revision: "aaa", Checksum: "bbb"},
+						{Revision: "ccc", Checksum: "ccc"},
+					},
+				},
+			},
+			artifacts: []*sourcev1.Artifact{
+				{Revision: "aaa", Checksum: "bbb"},
+			},
+			want: true,
+		},
+		{
+			name: "different number of include and included artifacts",
+			obj: sourcev1.GitRepository{
+				Spec: sourcev1.GitRepositorySpec{
+					Include: []sourcev1.GitRepositoryInclude{
+						{
+							GitRepositoryRef: meta.LocalObjectReference{Name: "foo"},
+							FromPath:         "bar",
+							ToPath:           "baz",
+						},
+						{
+							GitRepositoryRef: meta.LocalObjectReference{Name: "foo2"},
+							FromPath:         "bar",
+							ToPath:           "baz",
+						},
+					},
+				},
+				Status: sourcev1.GitRepositoryStatus{
+					ObservedInclude: []sourcev1.GitRepositoryInclude{
+						{
+							GitRepositoryRef: meta.LocalObjectReference{Name: "foo"},
+							FromPath:         "bar",
+							ToPath:           "baz",
+						},
+						{
+							GitRepositoryRef: meta.LocalObjectReference{Name: "foo2"},
+							FromPath:         "bar",
+							ToPath:           "baz",
+						},
+					},
+					IncludedArtifacts: []*sourcev1.Artifact{
+						{Revision: "aaa", Checksum: "bbb"},
+					},
+				},
+			},
+			artifacts: []*sourcev1.Artifact{
+				{Revision: "aaa", Checksum: "bbb"},
+				{Revision: "ccc", Checksum: "ccc"},
+			},
+			want: true,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			g := NewWithT(t)
+
+			includes := artifactSet(tt.artifacts)
+			g.Expect(gitContentConfigChanged(&tt.obj, &includes)).To(Equal(tt.want))
+		})
+	}
+}
diff --git a/controllers/helmchart_controller_test.go b/controllers/helmchart_controller_test.go
index 631286bc1..4f2c215bb 100644
--- a/controllers/helmchart_controller_test.go
+++ b/controllers/helmchart_controller_test.go
@@ -32,7 +32,6 @@ import (
 	"testing"
 	"time"
 
-	"github.com/darkowlzz/controller-check/status"
 	. "github.com/onsi/gomega"
 	hchart "helm.sh/helm/v3/pkg/chart"
 	"helm.sh/helm/v3/pkg/chart/loader"
@@ -50,8 +49,10 @@ import (
 	"github.com/fluxcd/pkg/apis/meta"
 	"github.com/fluxcd/pkg/helmtestserver"
 	"github.com/fluxcd/pkg/runtime/conditions"
+	conditionscheck "github.com/fluxcd/pkg/runtime/conditions/check"
 	"github.com/fluxcd/pkg/runtime/patch"
 	"github.com/fluxcd/pkg/testserver"
+
 	sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
 	serror "github.com/fluxcd/source-controller/internal/error"
 	"github.com/fluxcd/source-controller/internal/helm/chart"
@@ -108,8 +109,8 @@ func TestHelmChartReconciler_Reconcile(t *testing.T) {
 				}, timeout).Should(BeTrue())
 
 				// Check if the object status is valid.
-				condns := &status.Conditions{NegativePolarity: helmChartReadyCondition.NegativePolarity}
-				checker := status.NewChecker(testEnv.Client, condns)
+				condns := &conditionscheck.Conditions{NegativePolarity: helmChartReadyCondition.NegativePolarity}
+				checker := conditionscheck.NewChecker(testEnv.Client, condns)
 				checker.CheckErr(ctx, obj)
 
 				// kstatus client conformance check.
@@ -173,8 +174,8 @@ func TestHelmChartReconciler_Reconcile(t *testing.T) {
 				}, timeout).Should(BeTrue())
 
 				// Check if the object status is valid.
-				condns := &status.Conditions{NegativePolarity: helmChartReadyCondition.NegativePolarity}
-				checker := status.NewChecker(testEnv.Client, condns)
+				condns := &conditionscheck.Conditions{NegativePolarity: helmChartReadyCondition.NegativePolarity}
+				checker := conditionscheck.NewChecker(testEnv.Client, condns)
 				checker.CheckErr(ctx, obj)
 
 				g.Expect(testEnv.Delete(ctx, obj)).To(Succeed())
@@ -208,8 +209,8 @@ func TestHelmChartReconciler_Reconcile(t *testing.T) {
 				}, timeout).Should(BeTrue())
 
 				// Check if the object status is valid.
-				condns := &status.Conditions{NegativePolarity: helmChartReadyCondition.NegativePolarity}
-				checker := status.NewChecker(testEnv.Client, condns)
+				condns := &conditionscheck.Conditions{NegativePolarity: helmChartReadyCondition.NegativePolarity}
+				checker := conditionscheck.NewChecker(testEnv.Client, condns)
 				checker.CheckErr(ctx, obj)
 
 				g.Expect(testEnv.Delete(ctx, obj)).To(Succeed())
@@ -421,7 +422,7 @@ func TestHelmChartReconciler_reconcileSource(t *testing.T) {
 
 	tmpDir := t.TempDir()
 
-	storage, err := NewStorage(tmpDir, "example.com", retentionTTL, retentionRecords)
+	storage, err := NewStorage(tmpDir, "example.com", retentionTTL, retentionRecords, 0)
 	g.Expect(err).ToNot(HaveOccurred())
 
 	gitArtifact := &sourcev1.Artifact{
@@ -901,7 +902,7 @@ func TestHelmChartReconciler_buildFromOCIHelmRepository(t *testing.T) {
 	metadata, err := loadTestChartToOCI(chartData, chartPath, testRegistryServer)
 	g.Expect(err).NotTo(HaveOccurred())
 
-	storage, err := NewStorage(tmpDir, "example.com", retentionTTL, retentionRecords)
+	storage, err := NewStorage(tmpDir, "example.com", retentionTTL, retentionRecords, 0)
 	g.Expect(err).ToNot(HaveOccurred())
 
 	cachedArtifact := &sourcev1.Artifact{
@@ -1118,7 +1119,7 @@ func TestHelmChartReconciler_buildFromTarballArtifact(t *testing.T) {
 
 	tmpDir := t.TempDir()
 
-	storage, err := NewStorage(tmpDir, "example.com", retentionTTL, retentionRecords)
+	storage, err := NewStorage(tmpDir, "example.com", retentionTTL, retentionRecords, 0)
 	g.Expect(err).ToNot(HaveOccurred())
 
 	chartsArtifact := &sourcev1.Artifact{
diff --git a/controllers/helmrepository_controller_oci_test.go b/controllers/helmrepository_controller_oci_test.go
index f9c8356f3..953e1eee6 100644
--- a/controllers/helmrepository_controller_oci_test.go
+++ b/controllers/helmrepository_controller_oci_test.go
@@ -21,12 +21,6 @@ import (
 	"fmt"
 	"testing"
 
-	"github.com/darkowlzz/controller-check/status"
-	"github.com/fluxcd/pkg/apis/meta"
-	"github.com/fluxcd/pkg/runtime/conditions"
-	"github.com/fluxcd/pkg/runtime/patch"
-	sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
-	"github.com/fluxcd/source-controller/internal/helm/registry"
 	. "github.com/onsi/gomega"
 	corev1 "k8s.io/api/core/v1"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
@@ -36,6 +30,14 @@ import (
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
+
+	"github.com/fluxcd/pkg/apis/meta"
+	"github.com/fluxcd/pkg/runtime/conditions"
+	conditionscheck "github.com/fluxcd/pkg/runtime/conditions/check"
+	"github.com/fluxcd/pkg/runtime/patch"
+
+	sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
+	"github.com/fluxcd/source-controller/internal/helm/registry"
 )
 
 func TestHelmRepositoryOCIReconciler_Reconcile(t *testing.T) {
@@ -128,8 +130,8 @@ func TestHelmRepositoryOCIReconciler_Reconcile(t *testing.T) {
 			}, timeout).Should(BeTrue())
 
 			// Check if the object status is valid.
-			condns := &status.Conditions{NegativePolarity: helmRepositoryReadyCondition.NegativePolarity}
-			checker := status.NewChecker(testEnv.Client, condns)
+			condns := &conditionscheck.Conditions{NegativePolarity: helmRepositoryReadyCondition.NegativePolarity}
+			checker := conditionscheck.NewChecker(testEnv.Client, condns)
 			checker.CheckErr(ctx, obj)
 
 			// kstatus client conformance check.
diff --git a/controllers/helmrepository_controller_test.go b/controllers/helmrepository_controller_test.go
index 7b5525039..c2cb73a96 100644
--- a/controllers/helmrepository_controller_test.go
+++ b/controllers/helmrepository_controller_test.go
@@ -28,11 +28,6 @@ import (
 	"testing"
 	"time"
 
-	"github.com/darkowlzz/controller-check/status"
-	"github.com/fluxcd/pkg/apis/meta"
-	"github.com/fluxcd/pkg/helmtestserver"
-	"github.com/fluxcd/pkg/runtime/conditions"
-	"github.com/fluxcd/pkg/runtime/patch"
 	. "github.com/onsi/gomega"
 	helmgetter "helm.sh/helm/v3/pkg/getter"
 	corev1 "k8s.io/api/core/v1"
@@ -44,6 +39,12 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/client/fake"
 	fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
 
+	"github.com/fluxcd/pkg/apis/meta"
+	"github.com/fluxcd/pkg/helmtestserver"
+	"github.com/fluxcd/pkg/runtime/conditions"
+	conditionscheck "github.com/fluxcd/pkg/runtime/conditions/check"
+	"github.com/fluxcd/pkg/runtime/patch"
+
 	sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
 	"github.com/fluxcd/source-controller/internal/helm/getter"
 	"github.com/fluxcd/source-controller/internal/helm/repository"
@@ -101,8 +102,8 @@ func TestHelmRepositoryReconciler_Reconcile(t *testing.T) {
 	}, timeout).Should(BeTrue())
 
 	// Check if the object status is valid.
-	condns := &status.Conditions{NegativePolarity: helmRepositoryReadyCondition.NegativePolarity}
-	checker := status.NewChecker(testEnv.Client, condns)
+	condns := &conditionscheck.Conditions{NegativePolarity: helmRepositoryReadyCondition.NegativePolarity}
+	checker := conditionscheck.NewChecker(testEnv.Client, condns)
 	checker.CheckErr(ctx, obj)
 
 	// kstatus client conformance check.
@@ -1165,8 +1166,8 @@ func TestHelmRepositoryReconciler_ReconcileTypeUpdatePredicateFilter(t *testing.
 	}, timeout).Should(BeTrue())
 
 	// Check if the object status is valid.
-	condns := &status.Conditions{NegativePolarity: helmRepositoryReadyCondition.NegativePolarity}
-	checker := status.NewChecker(testEnv.Client, condns)
+	condns := &conditionscheck.Conditions{NegativePolarity: helmRepositoryReadyCondition.NegativePolarity}
+	checker := conditionscheck.NewChecker(testEnv.Client, condns)
 	checker.CheckErr(ctx, obj)
 
 	// kstatus client conformance check.
@@ -1217,8 +1218,8 @@ func TestHelmRepositoryReconciler_ReconcileTypeUpdatePredicateFilter(t *testing.
 	}, timeout).Should(BeTrue())
 
 	// Check if the object status is valid.
-	condns = &status.Conditions{NegativePolarity: helmRepositoryOCINegativeConditions}
-	checker = status.NewChecker(testEnv.Client, condns)
+	condns = &conditionscheck.Conditions{NegativePolarity: helmRepositoryOCINegativeConditions}
+	checker = conditionscheck.NewChecker(testEnv.Client, condns)
 	checker.CheckErr(ctx, obj)
 
 	g.Expect(testEnv.Delete(ctx, obj)).To(Succeed())
@@ -1282,8 +1283,8 @@ func TestHelmRepositoryReconciler_ReconcileSpecUpdatePredicateFilter(t *testing.
 	}, timeout).Should(BeTrue())
 
 	// Check if the object status is valid.
-	condns := &status.Conditions{NegativePolarity: helmRepositoryReadyCondition.NegativePolarity}
-	checker := status.NewChecker(testEnv.Client, condns)
+	condns := &conditionscheck.Conditions{NegativePolarity: helmRepositoryReadyCondition.NegativePolarity}
+	checker := conditionscheck.NewChecker(testEnv.Client, condns)
 	checker.CheckErr(ctx, obj)
 
 	// kstatus client conformance check.
@@ -1314,8 +1315,8 @@ func TestHelmRepositoryReconciler_ReconcileSpecUpdatePredicateFilter(t *testing.
 	}, timeout).Should(BeTrue())
 
 	// Check if the object status is valid.
-	condns = &status.Conditions{NegativePolarity: helmRepositoryReadyCondition.NegativePolarity}
-	checker = status.NewChecker(testEnv.Client, condns)
+	condns = &conditionscheck.Conditions{NegativePolarity: helmRepositoryReadyCondition.NegativePolarity}
+	checker = conditionscheck.NewChecker(testEnv.Client, condns)
 	checker.CheckErr(ctx, obj)
 
 	g.Expect(testEnv.Delete(ctx, obj)).To(Succeed())
diff --git a/controllers/ocirepository_controller.go b/controllers/ocirepository_controller.go
index 2a6d44429..677e6b6da 100644
--- a/controllers/ocirepository_controller.go
+++ b/controllers/ocirepository_controller.go
@@ -18,7 +18,6 @@ package controllers
 
 import (
 	"context"
-	"crypto/sha256"
 	"crypto/tls"
 	"crypto/x509"
 	"errors"
@@ -44,6 +43,7 @@ import (
 	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/apimachinery/pkg/util/sets"
 	kuberecorder "k8s.io/client-go/tools/record"
+	"k8s.io/utils/pointer"
 
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/builder"
@@ -427,10 +427,9 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
 		conditions.MarkTrue(obj, sourcev1.SourceVerifiedCondition, meta.SucceededReason, "verified signature of revision %s", revision)
 	}
 
-	// Skip pulling if the artifact revision and the content config checksum has
+	// Skip pulling if the artifact revision and the source configuration has
 	// not changed.
-	if obj.GetArtifact().HasRevision(revision) &&
-		r.calculateContentConfigChecksum(obj) == obj.Status.ContentConfigChecksum {
+	if obj.GetArtifact().HasRevision(revision) && !ociContentConfigChanged(obj) {
 		conditions.Delete(obj, sourcev1.FetchFailedCondition)
 		return sreconcile.ResultSuccess, nil
 	}
@@ -918,13 +917,9 @@ func (r *OCIRepositoryReconciler) reconcileArtifact(ctx context.Context, obj *so
 	artifact := r.Storage.NewArtifactFor(obj.Kind, obj, revision,
 		fmt.Sprintf("%s.tar.gz", r.digestFromRevision(revision)))
 
-	// Calculate the content config checksum.
-	ccc := r.calculateContentConfigChecksum(obj)
-
 	// Set the ArtifactInStorageCondition if there's no drift.
 	defer func() {
-		if obj.GetArtifact().HasRevision(artifact.Revision) &&
-			obj.Status.ContentConfigChecksum == ccc {
+		if obj.GetArtifact().HasRevision(artifact.Revision) && !ociContentConfigChanged(obj) {
 			conditions.Delete(obj, sourcev1.ArtifactOutdatedCondition)
 			conditions.MarkTrue(obj, sourcev1.ArtifactInStorageCondition, meta.SucceededReason,
 				"stored artifact for digest '%s'", artifact.Revision)
@@ -932,8 +927,7 @@ func (r *OCIRepositoryReconciler) reconcileArtifact(ctx context.Context, obj *so
 	}()
 
 	// The artifact is up-to-date
-	if obj.GetArtifact().HasRevision(artifact.Revision) &&
-		obj.Status.ContentConfigChecksum == ccc {
+	if obj.GetArtifact().HasRevision(artifact.Revision) && !ociContentConfigChanged(obj) {
 		r.eventLogf(ctx, obj, events.EventTypeTrace, sourcev1.ArtifactUpToDateReason,
 			"artifact up-to-date with remote revision: '%s'", artifact.Revision)
 		return sreconcile.ResultSuccess, nil
@@ -1008,10 +1002,12 @@ func (r *OCIRepositoryReconciler) reconcileArtifact(ctx context.Context, obj *so
 		}
 	}
 
-	// Record it on the object
+	// Record the observations on the object.
 	obj.Status.Artifact = artifact.DeepCopy()
 	obj.Status.Artifact.Metadata = metadata.Metadata
-	obj.Status.ContentConfigChecksum = ccc
+	obj.Status.ContentConfigChecksum = "" // To be removed in the next API version.
+	obj.Status.ObservedIgnore = obj.Spec.Ignore
+	obj.Status.ObservedLayerSelector = obj.Spec.LayerSelector
 
 	// Update symlink on a "best effort" basis
 	url, err := r.Storage.Symlink(artifact, "latest.tar.gz")
@@ -1141,24 +1137,6 @@ func (r *OCIRepositoryReconciler) notify(ctx context.Context, oldObj, newObj *so
 	}
 }
 
-// calculateContentConfigChecksum calculates a checksum of all the
-// configurations that result in a change in the source artifact. It can be used
-// to decide if further reconciliation is needed when an artifact already exists
-// for a set of configurations.
-func (r *OCIRepositoryReconciler) calculateContentConfigChecksum(obj *sourcev1.OCIRepository) string {
-	c := []byte{}
-	// Consider the ignore rules.
-	if obj.Spec.Ignore != nil {
-		c = append(c, []byte(*obj.Spec.Ignore)...)
-	}
-	// Consider the layer selector.
-	if obj.Spec.LayerSelector != nil {
-		c = append(c, []byte(obj.GetLayerMediaType()+obj.GetLayerOperation())...)
-	}
-
-	return fmt.Sprintf("sha256:%x", sha256.Sum256(c))
-}
-
 // craneOptions sets the auth headers, timeout and user agent
 // for all operations against remote container registries.
 func craneOptions(ctx context.Context, insecure bool) []crane.Option {
@@ -1208,3 +1186,31 @@ type remoteOptions struct {
 	craneOpts  []crane.Option
 	verifyOpts []remote.Option
 }
+
+// ociContentConfigChanged evaluates the current spec with the observations
+// of the artifact in the status to determine if artifact content configuration
+// has changed and requires rebuilding the artifact.
+func ociContentConfigChanged(obj *sourcev1.OCIRepository) bool {
+	if !pointer.StringEqual(obj.Spec.Ignore, obj.Status.ObservedIgnore) {
+		return true
+	}
+
+	if !layerSelectorEqual(obj.Spec.LayerSelector, obj.Status.ObservedLayerSelector) {
+		return true
+	}
+
+	return false
+}
+
+// Returns true if both arguments are nil or both arguments
+// dereference to the same value.
+// Based on k8s.io/utils/pointer/pointer.go pointer value equality.
+func layerSelectorEqual(a, b *sourcev1.OCILayerSelector) bool {
+	if (a == nil) != (b == nil) {
+		return false
+	}
+	if a == nil {
+		return true
+	}
+	return *a == *b
+}
diff --git a/controllers/ocirepository_controller_test.go b/controllers/ocirepository_controller_test.go
index bdd861120..b7932d1ad 100644
--- a/controllers/ocirepository_controller_test.go
+++ b/controllers/ocirepository_controller_test.go
@@ -37,7 +37,6 @@ import (
 	"testing"
 	"time"
 
-	"github.com/darkowlzz/controller-check/status"
 	"github.com/google/go-containerregistry/pkg/authn"
 	"github.com/google/go-containerregistry/pkg/crane"
 	"github.com/google/go-containerregistry/pkg/registry"
@@ -60,6 +59,7 @@ import (
 	"github.com/fluxcd/pkg/apis/meta"
 	"github.com/fluxcd/pkg/oci"
 	"github.com/fluxcd/pkg/runtime/conditions"
+	conditionscheck "github.com/fluxcd/pkg/runtime/conditions/check"
 	"github.com/fluxcd/pkg/runtime/patch"
 	"github.com/fluxcd/pkg/untar"
 
@@ -69,8 +69,6 @@ import (
 	"github.com/fluxcd/source-controller/pkg/git"
 )
 
-const ociRepoEmptyContentConfigChecksum = "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
-
 func TestOCIRepository_Reconcile(t *testing.T) {
 	g := NewWithT(t)
 
@@ -226,8 +224,8 @@ func TestOCIRepository_Reconcile(t *testing.T) {
 			}
 
 			// Check if the object status is valid
-			condns := &status.Conditions{NegativePolarity: ociRepositoryReadyCondition.NegativePolarity}
-			checker := status.NewChecker(testEnv.Client, condns)
+			condns := &conditionscheck.Conditions{NegativePolarity: ociRepositoryReadyCondition.NegativePolarity}
+			checker := conditionscheck.NewChecker(testEnv.Client, condns)
 			checker.CheckErr(ctx, obj)
 
 			// kstatus client conformance check
@@ -1290,21 +1288,48 @@ func TestOCIRepository_reconcileSource_noop(t *testing.T) {
 			},
 		},
 		{
-			name: "noop - artifact revisions and ccc match",
+			name: "noop - artifact revisions match",
 			beforeFunc: func(obj *sourcev1.OCIRepository) {
 				obj.Status.Artifact = &sourcev1.Artifact{
 					Revision: testRevision,
 				}
-				obj.Status.ContentConfigChecksum = ociRepoEmptyContentConfigChecksum
 			},
 			afterFunc: func(g *WithT, artifact *sourcev1.Artifact) {
 				g.Expect(artifact.Metadata).To(BeEmpty())
 			},
 		},
 		{
-			name: "full reconcile - same rev, different ccc",
+			name: "full reconcile - same rev, unobserved ignore",
 			beforeFunc: func(obj *sourcev1.OCIRepository) {
-				obj.Status.ContentConfigChecksum = "some-checksum"
+				obj.Status.ObservedIgnore = pointer.String("aaa")
+				obj.Status.Artifact = &sourcev1.Artifact{
+					Revision: testRevision,
+				}
+			},
+			afterFunc: func(g *WithT, artifact *sourcev1.Artifact) {
+				g.Expect(artifact.Metadata).ToNot(BeEmpty())
+			},
+		},
+		{
+			name: "noop - same rev, observed ignore",
+			beforeFunc: func(obj *sourcev1.OCIRepository) {
+				obj.Spec.Ignore = pointer.String("aaa")
+				obj.Status.ObservedIgnore = pointer.String("aaa")
+				obj.Status.Artifact = &sourcev1.Artifact{
+					Revision: testRevision,
+				}
+			},
+			afterFunc: func(g *WithT, artifact *sourcev1.Artifact) {
+				g.Expect(artifact.Metadata).To(BeEmpty())
+			},
+		},
+		{
+			name: "full reconcile - same rev, unobserved layer selector",
+			beforeFunc: func(obj *sourcev1.OCIRepository) {
+				obj.Spec.LayerSelector = &sourcev1.OCILayerSelector{
+					MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip",
+					Operation: sourcev1.OCILayerCopy,
+				}
 				obj.Status.Artifact = &sourcev1.Artifact{
 					Revision: testRevision,
 				}
@@ -1320,10 +1345,13 @@ func TestOCIRepository_reconcileSource_noop(t *testing.T) {
 					MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip",
 					Operation: sourcev1.OCILayerCopy,
 				}
+				obj.Status.ObservedLayerSelector = &sourcev1.OCILayerSelector{
+					MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip",
+					Operation: sourcev1.OCILayerCopy,
+				}
 				obj.Status.Artifact = &sourcev1.Artifact{
 					Revision: testRevision,
 				}
-				obj.Status.ContentConfigChecksum = "sha256:fcfd705e10431a341f2df5b05ecee1fb54facd9a5e88b0be82276bdf533b6c64"
 			},
 			afterFunc: func(g *WithT, artifact *sourcev1.Artifact) {
 				g.Expect(artifact.Metadata).To(BeEmpty())
@@ -1336,10 +1364,13 @@ func TestOCIRepository_reconcileSource_noop(t *testing.T) {
 					MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip",
 					Operation: sourcev1.OCILayerExtract,
 				}
+				obj.Status.ObservedLayerSelector = &sourcev1.OCILayerSelector{
+					MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip",
+					Operation: sourcev1.OCILayerCopy,
+				}
 				obj.Status.Artifact = &sourcev1.Artifact{
 					Revision: testRevision,
 				}
-				obj.Status.ContentConfigChecksum = "sha256:fcfd705e10431a341f2df5b05ecee1fb54facd9a5e88b0be82276bdf533b6c64"
 			},
 			afterFunc: func(g *WithT, artifact *sourcev1.Artifact) {
 				g.Expect(artifact.Metadata).ToNot(BeEmpty())
@@ -1449,7 +1480,6 @@ func TestOCIRepository_reconcileArtifact(t *testing.T) {
 				obj.Status.Artifact = &sourcev1.Artifact{
 					Revision: "revision",
 				}
-				obj.Status.ContentConfigChecksum = ociRepoEmptyContentConfigChecksum
 			},
 			assertArtifact: &sourcev1.Artifact{
 				Revision: "revision",
@@ -1467,14 +1497,13 @@ func TestOCIRepository_reconcileArtifact(t *testing.T) {
 			beforeFunc: func(obj *sourcev1.OCIRepository) {
 				obj.Status.Artifact = &sourcev1.Artifact{Revision: "revision"}
 				obj.Spec.Ignore = pointer.String("aaa")
-				obj.Status.ContentConfigChecksum = ociRepoEmptyContentConfigChecksum
 			},
 			want: sreconcile.ResultSuccess,
 			assertPaths: []string{
 				"latest.tar.gz",
 			},
 			afterFunc: func(g *WithT, obj *sourcev1.OCIRepository) {
-				g.Expect(obj.Status.ContentConfigChecksum).To(Equal("sha256:9834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f0"))
+				g.Expect(*obj.Status.ObservedIgnore).To(Equal("aaa"))
 			},
 			assertConditions: []metav1.Condition{
 				*conditions.TrueCondition(sourcev1.ArtifactInStorageCondition, meta.SucceededReason, "stored artifact for digest"),
@@ -1489,14 +1518,13 @@ func TestOCIRepository_reconcileArtifact(t *testing.T) {
 			beforeFunc: func(obj *sourcev1.OCIRepository) {
 				obj.Spec.LayerSelector = &sourcev1.OCILayerSelector{MediaType: "foo"}
 				obj.Status.Artifact = &sourcev1.Artifact{Revision: "revision"}
-				obj.Status.ContentConfigChecksum = ociRepoEmptyContentConfigChecksum
 			},
 			want: sreconcile.ResultSuccess,
 			assertPaths: []string{
 				"latest.tar.gz",
 			},
 			afterFunc: func(g *WithT, obj *sourcev1.OCIRepository) {
-				g.Expect(obj.Status.ContentConfigChecksum).To(Equal("sha256:82410edf339ab2945d97e26b92b6499e57156db63b94c17654b6ab97fbf86dbb"))
+				g.Expect(obj.Status.ObservedLayerSelector.MediaType).To(Equal("foo"))
 			},
 			assertConditions: []metav1.Condition{
 				*conditions.TrueCondition(sourcev1.ArtifactInStorageCondition, meta.SucceededReason, "stored artifact for digest"),
@@ -1515,14 +1543,14 @@ func TestOCIRepository_reconcileArtifact(t *testing.T) {
 					Operation: sourcev1.OCILayerCopy,
 				}
 				obj.Status.Artifact = &sourcev1.Artifact{Revision: "revision"}
-				obj.Status.ContentConfigChecksum = ociRepoEmptyContentConfigChecksum
 			},
 			want: sreconcile.ResultSuccess,
 			assertPaths: []string{
 				"latest.tar.gz",
 			},
 			afterFunc: func(g *WithT, obj *sourcev1.OCIRepository) {
-				g.Expect(obj.Status.ContentConfigChecksum).To(Equal("sha256:0e0e1c82f6403c8ee74fdf51349c8b5d98c508b5374c507c7ffb2e41dbc875df"))
+				g.Expect(obj.Status.ObservedLayerSelector.MediaType).To(Equal("foo"))
+				g.Expect(obj.Status.ObservedLayerSelector.Operation).To(Equal(sourcev1.OCILayerCopy))
 			},
 			assertConditions: []metav1.Condition{
 				*conditions.TrueCondition(sourcev1.ArtifactInStorageCondition, meta.SucceededReason, "stored artifact for digest"),
@@ -1538,7 +1566,8 @@ func TestOCIRepository_reconcileArtifact(t *testing.T) {
 				obj.Spec.Ignore = pointer.String("aaa")
 				obj.Spec.LayerSelector = &sourcev1.OCILayerSelector{MediaType: "foo"}
 				obj.Status.Artifact = &sourcev1.Artifact{Revision: "revision"}
-				obj.Status.ContentConfigChecksum = "sha256:0b56187b81cab6c3485583a46bec631f5ea08a1f69b769457f0e4aafb47884e3"
+				obj.Status.ObservedIgnore = pointer.String("aaa")
+				obj.Status.ObservedLayerSelector = &sourcev1.OCILayerSelector{MediaType: "foo"}
 			},
 			want: sreconcile.ResultSuccess,
 			assertArtifact: &sourcev1.Artifact{
@@ -2245,26 +2274,131 @@ func createTLSServer() (*httptest.Server, []byte, []byte, []byte, tls.Certificat
 	return srv, rootCertPEM, clientCertPEM, clientKeyPEM, clientTLSCert, err
 }
 
-func TestOCIRepository_calculateContentConfigChecksum(t *testing.T) {
-	g := NewWithT(t)
-	obj := &sourcev1.OCIRepository{}
-	r := &OCIRepositoryReconciler{}
+func TestOCIContentConfigChanged(t *testing.T) {
+	tests := []struct {
+		name   string
+		spec   sourcev1.OCIRepositorySpec
+		status sourcev1.OCIRepositoryStatus
+		want   bool
+	}{
+		{
+			name: "same ignore, no layer selector",
+			spec: sourcev1.OCIRepositorySpec{
+				Ignore: pointer.String("nnn"),
+			},
+			status: sourcev1.OCIRepositoryStatus{
+				ObservedIgnore: pointer.String("nnn"),
+			},
+			want: false,
+		},
+		{
+			name: "different ignore, no layer selector",
+			spec: sourcev1.OCIRepositorySpec{
+				Ignore: pointer.String("nnn"),
+			},
+			status: sourcev1.OCIRepositoryStatus{
+				ObservedIgnore: pointer.String("mmm"),
+			},
+			want: true,
+		},
+		{
+			name: "same ignore, same layer selector",
+			spec: sourcev1.OCIRepositorySpec{
+				Ignore: pointer.String("nnn"),
+				LayerSelector: &sourcev1.OCILayerSelector{
+					MediaType: "foo",
+					Operation: sourcev1.OCILayerExtract,
+				},
+			},
+			status: sourcev1.OCIRepositoryStatus{
+				ObservedIgnore: pointer.String("nnn"),
+				ObservedLayerSelector: &sourcev1.OCILayerSelector{
+					MediaType: "foo",
+					Operation: sourcev1.OCILayerExtract,
+				},
+			},
+			want: false,
+		},
+		{
+			name: "same ignore, different layer selector operation",
+			spec: sourcev1.OCIRepositorySpec{
+				Ignore: pointer.String("nnn"),
+				LayerSelector: &sourcev1.OCILayerSelector{
+					MediaType: "foo",
+					Operation: sourcev1.OCILayerCopy,
+				},
+			},
+			status: sourcev1.OCIRepositoryStatus{
+				ObservedIgnore: pointer.String("nnn"),
+				ObservedLayerSelector: &sourcev1.OCILayerSelector{
+					MediaType: "foo",
+					Operation: sourcev1.OCILayerExtract,
+				},
+			},
+			want: true,
+		},
+		{
+			name: "same ignore, different layer selector mediatype",
+			spec: sourcev1.OCIRepositorySpec{
+				Ignore: pointer.String("nnn"),
+				LayerSelector: &sourcev1.OCILayerSelector{
+					MediaType: "bar",
+					Operation: sourcev1.OCILayerExtract,
+				},
+			},
+			status: sourcev1.OCIRepositoryStatus{
+				ObservedIgnore: pointer.String("nnn"),
+				ObservedLayerSelector: &sourcev1.OCILayerSelector{
+					MediaType: "foo",
+					Operation: sourcev1.OCILayerExtract,
+				},
+			},
+			want: true,
+		},
+		{
+			name: "no ignore, same layer selector",
+			spec: sourcev1.OCIRepositorySpec{
+				LayerSelector: &sourcev1.OCILayerSelector{
+					MediaType: "foo",
+					Operation: sourcev1.OCILayerExtract,
+				},
+			},
+			status: sourcev1.OCIRepositoryStatus{
+				ObservedLayerSelector: &sourcev1.OCILayerSelector{
+					MediaType: "foo",
+					Operation: sourcev1.OCILayerExtract,
+				},
+			},
+			want: false,
+		},
+		{
+			name: "no ignore, different layer selector",
+			spec: sourcev1.OCIRepositorySpec{
+				LayerSelector: &sourcev1.OCILayerSelector{
+					MediaType: "bar",
+					Operation: sourcev1.OCILayerExtract,
+				},
+			},
+			status: sourcev1.OCIRepositoryStatus{
+				ObservedLayerSelector: &sourcev1.OCILayerSelector{
+					MediaType: "foo",
+					Operation: sourcev1.OCILayerExtract,
+				},
+			},
+			want: true,
+		},
+	}
 
-	emptyChecksum := r.calculateContentConfigChecksum(obj)
-	g.Expect(emptyChecksum).To(Equal(ociRepoEmptyContentConfigChecksum))
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			g := NewWithT(t)
 
-	// Ignore modified.
-	obj.Spec.Ignore = pointer.String("some-rule")
-	ignoreModChecksum := r.calculateContentConfigChecksum(obj)
-	g.Expect(emptyChecksum).ToNot(Equal(ignoreModChecksum))
+			obj := &sourcev1.OCIRepository{
+				Spec:   tt.spec,
+				Status: tt.status,
+			}
 
-	// LayerSelector modified.
-	obj.Spec.LayerSelector = &sourcev1.OCILayerSelector{
-		MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip",
+			g.Expect(ociContentConfigChanged(obj)).To(Equal(tt.want))
+		})
 	}
-	mediaTypeChecksum := r.calculateContentConfigChecksum(obj)
-	g.Expect(ignoreModChecksum).ToNot(Equal(mediaTypeChecksum))
-	obj.Spec.LayerSelector.Operation = sourcev1.OCILayerCopy
-	layerCopyChecksum := r.calculateContentConfigChecksum(obj)
-	g.Expect(mediaTypeChecksum).ToNot(Equal(layerCopyChecksum))
 }
diff --git a/controllers/storage.go b/controllers/storage.go
index ce7c6841d..0c9c1e959 100644
--- a/controllers/storage.go
+++ b/controllers/storage.go
@@ -61,10 +61,14 @@ type Storage struct {
 	// ArtifactRetentionRecords is the maximum number of artifacts to be kept in
 	// storage after a garbage collection.
 	ArtifactRetentionRecords int `json:"artifactRetentionRecords"`
+
+	// ArtifactMaxSize sets the max size in bytes for an artifact contents.
+	// Setting the value to zero or a negative value, disables the limit.
+	ArtifactMaxSize int64 `json:"artifactMaxSize"`
 }
 
 // NewStorage creates the storage helper for a given path and hostname.
-func NewStorage(basePath string, hostname string, artifactRetentionTTL time.Duration, artifactRetentionRecords int) (*Storage, error) {
+func NewStorage(basePath string, hostname string, artifactRetentionTTL time.Duration, artifactRetentionRecords int, artifactMaxSize int64) (*Storage, error) {
 	if f, err := os.Stat(basePath); os.IsNotExist(err) || !f.IsDir() {
 		return nil, fmt.Errorf("invalid dir path: %s", basePath)
 	}
@@ -73,6 +77,7 @@ func NewStorage(basePath string, hostname string, artifactRetentionTTL time.Dura
 		Hostname:                 hostname,
 		ArtifactRetentionTTL:     artifactRetentionTTL,
 		ArtifactRetentionRecords: artifactRetentionRecords,
+		ArtifactMaxSize:          artifactMaxSize,
 	}, nil
 }
 
@@ -432,6 +437,11 @@ func (s *Storage) Archive(artifact *sourcev1.Artifact, dir string, filter Archiv
 		return err
 	}
 
+	if s.ArtifactMaxSize > 0 && sz.written > s.ArtifactMaxSize {
+		return fmt.Errorf("artifact size %d exceeds the max limit of %d bytes, use .sourceignore to exclude files from the artifact",
+			sz.written, s.ArtifactMaxSize)
+	}
+
 	if err := os.Chmod(tmpName, 0o600); err != nil {
 		return err
 	}
diff --git a/controllers/storage_test.go b/controllers/storage_test.go
index 8e0e599a6..cc61c1463 100644
--- a/controllers/storage_test.go
+++ b/controllers/storage_test.go
@@ -38,7 +38,7 @@ import (
 func TestStorageConstructor(t *testing.T) {
 	dir := t.TempDir()
 
-	if _, err := NewStorage("/nonexistent", "hostname", time.Minute, 2); err == nil {
+	if _, err := NewStorage("/nonexistent", "hostname", time.Minute, 2, 0); err == nil {
 		t.Fatal("nonexistent path was allowable in storage constructor")
 	}
 
@@ -48,13 +48,13 @@ func TestStorageConstructor(t *testing.T) {
 	}
 	f.Close()
 
-	if _, err := NewStorage(f.Name(), "hostname", time.Minute, 2); err == nil {
+	if _, err := NewStorage(f.Name(), "hostname", time.Minute, 2, 0); err == nil {
 		os.Remove(f.Name())
 		t.Fatal("file path was accepted as basedir")
 	}
 	os.Remove(f.Name())
 
-	if _, err := NewStorage(dir, "hostname", time.Minute, 2); err != nil {
+	if _, err := NewStorage(dir, "hostname", time.Minute, 2, 0); err != nil {
 		t.Fatalf("Valid path did not successfully return: %v", err)
 	}
 }
@@ -103,7 +103,7 @@ func walkTar(tarFile string, match string, dir bool) (int64, bool, error) {
 func TestStorage_Archive(t *testing.T) {
 	dir := t.TempDir()
 
-	storage, err := NewStorage(dir, "hostname", time.Minute, 2)
+	storage, err := NewStorage(dir, "hostname", time.Minute, 2, 0)
 	if err != nil {
 		t.Fatalf("error while bootstrapping storage: %v", err)
 	}
@@ -263,7 +263,7 @@ func TestStorageRemoveAllButCurrent(t *testing.T) {
 	t.Run("bad directory in archive", func(t *testing.T) {
 		dir := t.TempDir()
 
-		s, err := NewStorage(dir, "hostname", time.Minute, 2)
+		s, err := NewStorage(dir, "hostname", time.Minute, 2, 0)
 		if err != nil {
 			t.Fatalf("Valid path did not successfully return: %v", err)
 		}
@@ -277,7 +277,7 @@ func TestStorageRemoveAllButCurrent(t *testing.T) {
 		g := NewWithT(t)
 		dir := t.TempDir()
 
-		s, err := NewStorage(dir, "hostname", time.Minute, 2)
+		s, err := NewStorage(dir, "hostname", time.Minute, 2, 0)
 		g.Expect(err).ToNot(HaveOccurred(), "failed to create new storage")
 
 		artifact := sourcev1.Artifact{
@@ -338,7 +338,7 @@ func TestStorageRemoveAll(t *testing.T) {
 			g := NewWithT(t)
 			dir := t.TempDir()
 
-			s, err := NewStorage(dir, "hostname", time.Minute, 2)
+			s, err := NewStorage(dir, "hostname", time.Minute, 2, 0)
 			g.Expect(err).ToNot(HaveOccurred(), "failed to create new storage")
 
 			artifact := sourcev1.Artifact{
@@ -364,7 +364,7 @@ func TestStorageCopyFromPath(t *testing.T) {
 
 	dir := t.TempDir()
 
-	storage, err := NewStorage(dir, "hostname", time.Minute, 2)
+	storage, err := NewStorage(dir, "hostname", time.Minute, 2, 0)
 	if err != nil {
 		t.Fatalf("error while bootstrapping storage: %v", err)
 	}
@@ -542,7 +542,7 @@ func TestStorage_getGarbageFiles(t *testing.T) {
 			g := NewWithT(t)
 			dir := t.TempDir()
 
-			s, err := NewStorage(dir, "hostname", tt.ttl, tt.maxItemsToBeRetained)
+			s, err := NewStorage(dir, "hostname", tt.ttl, tt.maxItemsToBeRetained, 0)
 			g.Expect(err).ToNot(HaveOccurred(), "failed to create new storage")
 
 			artifact := sourcev1.Artifact{
@@ -616,7 +616,7 @@ func TestStorage_GarbageCollect(t *testing.T) {
 			g := NewWithT(t)
 			dir := t.TempDir()
 
-			s, err := NewStorage(dir, "hostname", time.Second*2, 2)
+			s, err := NewStorage(dir, "hostname", time.Second*2, 2, 0)
 			g.Expect(err).ToNot(HaveOccurred(), "failed to create new storage")
 
 			artifact := sourcev1.Artifact{
@@ -658,3 +658,90 @@ func TestStorage_GarbageCollect(t *testing.T) {
 		})
 	}
 }
+
+func TestStorage_MaxSize(t *testing.T) {
+	createFiles := func(files map[string][]byte) (dir string, err error) {
+		dir = t.TempDir()
+		for name, b := range files {
+			absPath := filepath.Join(dir, name)
+			if err = os.MkdirAll(filepath.Dir(absPath), 0o750); err != nil {
+				return
+			}
+			f, err := os.Create(absPath)
+			if err != nil {
+				return "", fmt.Errorf("could not create file %q: %w", absPath, err)
+			}
+			if n, err := f.Write(b); err != nil {
+				f.Close()
+				return "", fmt.Errorf("could not write %d bytes to file %q: %w", n, f.Name(), err)
+			}
+			f.Close()
+		}
+		return
+	}
+
+	tests := []struct {
+		name         string
+		files        map[string][]byte
+		maxSize      int64
+		wantErrMatch string
+	}{
+		{
+			name: "creates artifact without size limit",
+			files: map[string][]byte{
+				"test.txt":  []byte(`contents`),
+				"test.yaml": []byte(`a: b`),
+			},
+			maxSize:      -1,
+			wantErrMatch: "",
+		},
+		{
+			name: "fails to create artifact due to size limit",
+			files: map[string][]byte{
+				"test.txt":  []byte(`contents`),
+				"test.yaml": []byte(`a: b`),
+			},
+			maxSize:      200,
+			wantErrMatch: "exceeds the max limit",
+		},
+		{
+			name: "creates artifact in the size limit range",
+			files: map[string][]byte{
+				"test.txt":  []byte(`contents`),
+				"test.yaml": []byte(`a: b`),
+			},
+			maxSize: 300,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			g := NewWithT(t)
+
+			dir, err := createFiles(tt.files)
+			if err != nil {
+				t.Error(err)
+				return
+			}
+			defer os.RemoveAll(dir)
+
+			artifact := sourcev1.Artifact{
+				Path: filepath.Join(randStringRunes(10), randStringRunes(10), randStringRunes(10)+".tar.gz"),
+			}
+
+			s, err := NewStorage(dir, "hostname", time.Second*2, 2, tt.maxSize)
+			g.Expect(err).ToNot(HaveOccurred(), "failed to create new storage")
+
+			if err := s.MkdirAll(artifact); err != nil {
+				t.Fatalf("artifact directory creation failed: %v", err)
+			}
+
+			err = s.Archive(&artifact, dir, SourceIgnoreFilter(nil, nil))
+			if tt.wantErrMatch == "" {
+				g.Expect(err).ToNot(HaveOccurred())
+			} else {
+				g.Expect(err.Error()).To(ContainSubstring(tt.wantErrMatch))
+			}
+		})
+	}
+}
diff --git a/controllers/suite_test.go b/controllers/suite_test.go
index 8654f06f4..249a43e81 100644
--- a/controllers/suite_test.go
+++ b/controllers/suite_test.go
@@ -354,7 +354,7 @@ func initTestTLS() {
 }
 
 func newTestStorage(s *testserver.HTTPServer) (*Storage, error) {
-	storage, err := NewStorage(s.Root(), s.URL(), retentionTTL, retentionRecords)
+	storage, err := NewStorage(s.Root(), s.URL(), retentionTTL, retentionRecords, 0)
 	if err != nil {
 		return nil, err
 	}
diff --git a/docs/api/source.md b/docs/api/source.md
index 8c4eda2ee..d5762fc30 100644
--- a/docs/api/source.md
+++ b/docs/api/source.md
@@ -1518,6 +1518,19 @@ Artifact
 </tr>
 <tr>
 <td>
+<code>observedIgnore</code><br>
+<em>
+string
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>ObservedIgnore is the observed exclusion patterns used for constructing
+the source artifact.</p>
+</td>
+</tr>
+<tr>
+<td>
 <code>ReconcileRequestStatus</code><br>
 <em>
 <a href="https://godoc.org/github.com/fluxcd/pkg/apis/meta#ReconcileRequestStatus">
@@ -1539,7 +1552,8 @@ github.com/fluxcd/pkg/apis/meta.ReconcileRequestStatus
 </h3>
 <p>
 (<em>Appears on:</em>
-<a href="#source.toolkit.fluxcd.io/v1beta2.GitRepositorySpec">GitRepositorySpec</a>)
+<a href="#source.toolkit.fluxcd.io/v1beta2.GitRepositorySpec">GitRepositorySpec</a>, 
+<a href="#source.toolkit.fluxcd.io/v1beta2.GitRepositoryStatus">GitRepositoryStatus</a>)
 </p>
 <p>GitRepositoryInclude specifies a local reference to a GitRepository which
 Artifact (sub-)contents must be included, and where they should be placed.</p>
@@ -1969,6 +1983,49 @@ observed in .status.observedGeneration version of the object. This can
 be used to determine if the content of the included repository has
 changed.
 It has the format of <code>&lt;algo&gt;:&lt;checksum&gt;</code>, for example: <code>sha256:&lt;checksum&gt;</code>.</p>
+<p>Deprecated: Replaced with explicit fields for observed artifact content
+config in the status.</p>
+</td>
+</tr>
+<tr>
+<td>
+<code>observedIgnore</code><br>
+<em>
+string
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>ObservedIgnore is the observed exclusion patterns used for constructing
+the source artifact.</p>
+</td>
+</tr>
+<tr>
+<td>
+<code>observedRecurseSubmodules</code><br>
+<em>
+bool
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>ObservedRecurseSubmodules is the observed resource submodules
+configuration used to produce the current Artifact.</p>
+</td>
+</tr>
+<tr>
+<td>
+<code>observedInclude</code><br>
+<em>
+<a href="#source.toolkit.fluxcd.io/v1beta2.GitRepositoryInclude">
+[]GitRepositoryInclude
+</a>
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>ObservedInclude is the observed list of GitRepository resources used to
+to produce the current Artifact.</p>
 </td>
 </tr>
 <tr>
@@ -2608,7 +2665,8 @@ string
 </h3>
 <p>
 (<em>Appears on:</em>
-<a href="#source.toolkit.fluxcd.io/v1beta2.OCIRepositorySpec">OCIRepositorySpec</a>)
+<a href="#source.toolkit.fluxcd.io/v1beta2.OCIRepositorySpec">OCIRepositorySpec</a>, 
+<a href="#source.toolkit.fluxcd.io/v1beta2.OCIRepositoryStatus">OCIRepositoryStatus</a>)
 </p>
 <p>OCILayerSelector specifies which layer should be extracted from an OCI Artifact</p>
 <div class="md-typeset__scrollwrap">
@@ -3010,6 +3068,36 @@ observed in .status.observedGeneration version of the object. This can
 be used to determine if the content configuration has changed and the
 artifact needs to be rebuilt.
 It has the format of <code>&lt;algo&gt;:&lt;checksum&gt;</code>, for example: <code>sha256:&lt;checksum&gt;</code>.</p>
+<p>Deprecated: Replaced with explicit fields for observed artifact content
+config in the status.</p>
+</td>
+</tr>
+<tr>
+<td>
+<code>observedIgnore</code><br>
+<em>
+string
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>ObservedIgnore is the observed exclusion patterns used for constructing
+the source artifact.</p>
+</td>
+</tr>
+<tr>
+<td>
+<code>observedLayerSelector</code><br>
+<em>
+<a href="#source.toolkit.fluxcd.io/v1beta2.OCILayerSelector">
+OCILayerSelector
+</a>
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>ObservedLayerSelector is the observed layer selector used for constructing
+the source artifact.</p>
 </td>
 </tr>
 <tr>
diff --git a/docs/spec/v1beta2/buckets.md b/docs/spec/v1beta2/buckets.md
index 0e8e5270b..23c036fdf 100644
--- a/docs/spec/v1beta2/buckets.md
+++ b/docs/spec/v1beta2/buckets.md
@@ -1064,6 +1064,25 @@ Note that a Bucket can be [reconciling](#reconciling-bucket) while failing at
 the same time, for example due to a newly introduced configuration issue in the
 Bucket spec.
 
+### Observed Ignore
+
+The source-controller reports an observed ignore in the Bucket's
+`.status.observedIgnore`. The observed ignore is the latest `.spec.ignore` value
+which resulted in a [ready state](#ready-bucket), or stalled due to error
+it can not recover from without human intervention. The value is the same as the
+[ignore in spec](#ignore). It indicates the ignore rules used in building the
+current artifact in storage.
+
+Example:
+```yaml
+status:
+  ...
+  observedIgnore: |
+    hpa.yaml
+    build
+  ...
+```
+
 ### Observed Generation
 
 The source-controller reports an
diff --git a/docs/spec/v1beta2/gitrepositories.md b/docs/spec/v1beta2/gitrepositories.md
index a25569422..7cfbfd18b 100644
--- a/docs/spec/v1beta2/gitrepositories.md
+++ b/docs/spec/v1beta2/gitrepositories.md
@@ -854,6 +854,79 @@ configurations of the GitRepository that indicate a change in source and
 records it in `.status.contentConfigChecksum`. This field is used to determine
 if the source artifact needs to be rebuilt.
 
+**Deprecation Note:** `contentConfigChecksum` is no longer used and will be
+removed in the next API version. The individual components used for generating
+content configuration checksum now have explicit fields in the status. This
+makes the observations used by the controller for making artifact rebuild
+decisions more transparent and easier to debug.
+
+### Observed Ignore
+
+The source-controller reports an observed ignore in the GitRepository's
+`.status.observedIgnore`. The observed ignore is the latest `.spec.ignore` value
+which resulted in a [ready state](#ready-gitrepository), or stalled due to error
+it can not recover from without human intervention.
+The value is the same as the [ignore in spec](#ignore).
+It indicates the ignore rules used in building the current artifact in storage.
+It is also used by the controller to determine if an artifact needs to be
+rebuilt.
+
+Example:
+```yaml
+status:
+  ...
+  observedIgnore: |
+    cue
+    pkg
+  ...
+```
+
+### Observed Recurse Submodules
+
+The source-controller reports an observed recurse submodule in the
+GitRepository's `.status.observedRecurseSubmodules`. The observed recurse
+submodules is the latest `.spec.recurseSubmodules` value which resulted in a
+[ready state](#ready-gitrepository), or stalled due to error it can not recover
+from without human intervention. The value is the same as the
+[recurse submodules in spec](#recurse-submodules). It indicates the recurse
+submodules configuration used in building the current artifact in storage. It is
+also used by the controller to determine if an artifact needs to be rebuilt.
+
+Example:
+```yaml
+status:
+  ...
+  observedRecurseSubmodules: true
+  ...
+```
+
+### Observed Include
+
+The source-controller reports observed include in the GitRepository's
+`.status.observedInclude`. The observed include is the latest
+`.spec.recurseSubmodules` value which resulted in a
+[ready state](#ready-gitrepository), or stalled due to error it can not recover
+from without human intervention. The value is the same as the
+[include in spec](#include). It indicates the include configuration used in
+building the current artifact in storage. It is also used by the controller to
+determine if an artifact needs to be rebuilt.
+
+Example:
+```yaml
+status:
+  ...
+  observedInclude:
+  - fromPath: deploy/webapp
+    repository:
+      name: repo1
+    toPath: foo
+  - fromPath: deploy/secure
+    repository:
+      name: repo2
+    toPath: bar
+  ...
+```
+
 ### Observed Generation
 
 The source-controller reports an [observed generation][typical-status-properties]
diff --git a/docs/spec/v1beta2/ocirepositories.md b/docs/spec/v1beta2/ocirepositories.md
index 76cc73866..0320e8e5a 100644
--- a/docs/spec/v1beta2/ocirepositories.md
+++ b/docs/spec/v1beta2/ocirepositories.md
@@ -868,6 +868,53 @@ configurations of the OCIRepository that indicate a change in source and
 records it in `.status.contentConfigChecksum`. This field is used to determine
 if the source artifact needs to be rebuilt.
 
+**Deprecation Note:** `contentConfigChecksum` is no longer used and will be
+removed in the next API version. The individual components used for generating
+content configuration checksum now have explicit fields in the status. This
+makes the observations used by the controller for making artifact rebuild
+decisions more transparent and easier to debug.
+
+### Observed Ignore
+
+The source-controller reports an observed ignore in the OCIRepository's
+`.status.observedIgnore`. The observed ignore is the latest `.spec.ignore` value
+which resulted in a [ready state](#ready-ocirepository), or stalled due to error
+it can not recover from without human intervention. The value is the same as the
+[ignore in spec](#ignore). It indicates the ignore rules used in building the
+current artifact in storage. It is also used by the controller to determine if
+an artifact needs to be rebuilt.
+
+Example:
+```yaml
+status:
+  ...
+  observedIgnore: |
+    hpa.yaml
+    build
+  ...
+```
+
+### Observed Layer Selector
+
+The source-controller reports an observed layer selector in the OCIRepository's
+`.status.observedLayerSelector`. The observed layer selector is the latest
+`.spec.layerSelector` value which resulted in a [ready state](#ready-ocirepository),
+or stalled due to error it can not recover from without human intervention.
+The value is the same as the [layer selector in spec](#layer-selector).
+It indicates the layer selection configuration used in building the current
+artifact in storage. It is also used by the controller to determine if an
+artifact needs to be rebuilt.
+
+Example:
+```yaml
+status:
+  ...
+  observedLayerSelector:
+    mediaType: application/vnd.docker.image.rootfs.diff.tar.gzip
+    operation: copy
+  ...
+```
+
 ### Observed Generation
 
 The source-controller reports an [observed generation][typical-status-properties]
diff --git a/go.mod b/go.mod
index 8e2c986b3..abe4dbb1c 100644
--- a/go.mod
+++ b/go.mod
@@ -24,26 +24,25 @@ require (
 	// When in doubt (and not using openpgp), use /x/crypto.
 	github.com/ProtonMail/go-crypto v0.0.0-20220824120805-4b6e5c587895
 	github.com/cyphar/filepath-securejoin v0.2.3
-	github.com/darkowlzz/controller-check v0.0.0-20220902134353-b2ee0ae3566c
 	github.com/distribution/distribution/v3 v3.0.0-20220907155224-78b9c98c5c31
 	github.com/docker/cli v20.10.18+incompatible
 	github.com/docker/go-units v0.5.0
 	github.com/elazarl/goproxy v0.0.0-20220901064549-fbd10ff4f5a1
 	github.com/fluxcd/gitkit v0.6.0
-	github.com/fluxcd/pkg/apis/meta v0.16.0
+	github.com/fluxcd/pkg/apis/meta v0.17.0
 	github.com/fluxcd/pkg/gittestserver v0.7.0
 	github.com/fluxcd/pkg/gitutil v0.2.0
 	github.com/fluxcd/pkg/helmtestserver v0.9.0
 	github.com/fluxcd/pkg/lockedfile v0.1.0
 	github.com/fluxcd/pkg/masktoken v0.2.0
-	github.com/fluxcd/pkg/oci v0.11.0
-	github.com/fluxcd/pkg/runtime v0.19.0
+	github.com/fluxcd/pkg/oci v0.13.0
+	github.com/fluxcd/pkg/runtime v0.21.0
 	github.com/fluxcd/pkg/sourceignore v0.2.0
 	github.com/fluxcd/pkg/ssh v0.6.0
 	github.com/fluxcd/pkg/testserver v0.3.0
 	github.com/fluxcd/pkg/untar v0.2.0
 	github.com/fluxcd/pkg/version v0.2.0
-	github.com/fluxcd/source-controller/api v0.30.0
+	github.com/fluxcd/source-controller/api v0.30.1
 	github.com/go-git/go-billy/v5 v5.3.1
 	github.com/go-git/go-git/v5 v5.4.2
 	github.com/go-logr/logr v1.2.3
@@ -389,7 +388,7 @@ require (
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 	k8s.io/apiextensions-apiserver v0.25.0 // indirect
 	k8s.io/apiserver v0.25.0 // indirect
-	k8s.io/cli-runtime v0.25.0 // indirect
+	k8s.io/cli-runtime v0.25.2 // indirect
 	k8s.io/component-base v0.25.2 // indirect
 	k8s.io/klog/v2 v2.80.1 // indirect
 	k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect
diff --git a/go.sum b/go.sum
index ea5e1ef4f..bcfb2455c 100644
--- a/go.sum
+++ b/go.sum
@@ -424,8 +424,6 @@ github.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X
 github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
 github.com/danieljoos/wincred v1.0.2/go.mod h1:SnuYRW9lp1oJrZX/dXJqr0cPK5gYXqx3EJbmjhLdK9U=
 github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg=
-github.com/darkowlzz/controller-check v0.0.0-20220902134353-b2ee0ae3566c h1:fhuDA5Xr1bCEJDVGMn2luGjS1aLhj48nPHUMVp5oA7Q=
-github.com/darkowlzz/controller-check v0.0.0-20220902134353-b2ee0ae3566c/go.mod h1:0G1Hgd/faRl0UANlS9s8bULzwcU1+8pY8pEmI61t9d8=
 github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -516,8 +514,8 @@ github.com/fluxcd/gitkit v0.6.0 h1:iNg5LTx6ePo+Pl0ZwqHTAkhbUHxGVSY3YCxCdw7VIFg=
 github.com/fluxcd/gitkit v0.6.0/go.mod h1:svOHuKi0fO9HoawdK4HfHAJJseZDHHjk7I3ihnCIqNo=
 github.com/fluxcd/pkg/apis/acl v0.1.0 h1:EoAl377hDQYL3WqanWCdifauXqXbMyFuK82NnX6pH4Q=
 github.com/fluxcd/pkg/apis/acl v0.1.0/go.mod h1:zfEZzz169Oap034EsDhmCAGgnWlcWmIObZjYMusoXS8=
-github.com/fluxcd/pkg/apis/meta v0.16.0 h1:6Mj9rB0TtvCeTe3IlQDc1i2DH75Oosea9yUqS7XafVg=
-github.com/fluxcd/pkg/apis/meta v0.16.0/go.mod h1:GrOVzWXiu22XjLNgLLe2EBYhQPqZetes5SIADb4bmHE=
+github.com/fluxcd/pkg/apis/meta v0.17.0 h1:Y2dfo1syHZDb9Mexjr2SWdcj1FnxnRXm015hEnhl6wU=
+github.com/fluxcd/pkg/apis/meta v0.17.0/go.mod h1:GrOVzWXiu22XjLNgLLe2EBYhQPqZetes5SIADb4bmHE=
 github.com/fluxcd/pkg/gittestserver v0.7.0 h1:PRVaEjeC/ePKTusB5Bx/ExM0P6bjroPdG6K2DO7YJUM=
 github.com/fluxcd/pkg/gittestserver v0.7.0/go.mod h1:WHqqZQfdePi5M/s1ONMTB4MigktqJhzAFJOZ0KTBw9Y=
 github.com/fluxcd/pkg/gitutil v0.2.0 h1:7vvXfq+Ur1/WXEejXY/b2haJ/2Uj5Et5v4V33l+ni1Q=
@@ -528,10 +526,10 @@ github.com/fluxcd/pkg/lockedfile v0.1.0 h1:YsYFAkd6wawMCcD74ikadAKXA4s2sukdxrn7w
 github.com/fluxcd/pkg/lockedfile v0.1.0/go.mod h1:EJLan8t9MiOcgTs8+puDjbE6I/KAfHbdvIy9VUgIjm8=
 github.com/fluxcd/pkg/masktoken v0.2.0 h1:HoSPTk4l1fz5Fevs2vVRvZGru33blfMwWSZKsHdfG/0=
 github.com/fluxcd/pkg/masktoken v0.2.0/go.mod h1:EA7GleAHL33kN6kTW06m5R3/Q26IyuGO7Ef/0CtpDI0=
-github.com/fluxcd/pkg/oci v0.11.0 h1:mMZmF1zwUpM/Nq77aHwhiDmiOhOy3KbbBx0ZS1rOycU=
-github.com/fluxcd/pkg/oci v0.11.0/go.mod h1:gsRwVj0gTwk9xF3PuPJQ4R+rv8UtT26Gi7r1XfyBw8A=
-github.com/fluxcd/pkg/runtime v0.19.0 h1:4lRlnZfJFhWvuaNWgNsAkPQg09633xCRCf9d0SgXIWk=
-github.com/fluxcd/pkg/runtime v0.19.0/go.mod h1:9Kh46LjwQeUu6o1DUQulLGyo5e5wfQxeFf4ONNobT3U=
+github.com/fluxcd/pkg/oci v0.13.0 h1:447gUqqh+QnvLt+JCuqu1CjDpsbJhmyB1eibbiH7gTQ=
+github.com/fluxcd/pkg/oci v0.13.0/go.mod h1:gsRwVj0gTwk9xF3PuPJQ4R+rv8UtT26Gi7r1XfyBw8A=
+github.com/fluxcd/pkg/runtime v0.21.0 h1:3u6z8M1fDJDGzyAUHWanWy7xF7xQnn7jl2wTzsvU3Pg=
+github.com/fluxcd/pkg/runtime v0.21.0/go.mod h1:Cm6jIhltzXIM3CRRY6SFASDn+z2m/1yPqOWwD73c3io=
 github.com/fluxcd/pkg/sourceignore v0.2.0 h1:ooNbIkfxqNB+KKiY4AU+/DxwzjIKIOWBRK1As5QFlug=
 github.com/fluxcd/pkg/sourceignore v0.2.0/go.mod h1:m9/q+YLMNSWjXns1n/5q3ucwzSSddti+D6ExbNaCo6s=
 github.com/fluxcd/pkg/ssh v0.6.0 h1:yRJ866obXCo0JseJCqlGKtF8cumioPcwIC6kMwf8Spg=
@@ -2443,8 +2441,8 @@ k8s.io/apimachinery v0.25.2 h1:WbxfAjCx+AeN8Ilp9joWnyJ6xu9OMeS/fsfjK/5zaQs=
 k8s.io/apimachinery v0.25.2/go.mod h1:hqqA1X0bsgsxI6dXsJ4HnNTBOmJNxyPp8dw3u2fSHwA=
 k8s.io/apiserver v0.25.0 h1:8kl2ifbNffD440MyvHtPaIz1mw4mGKVgWqM0nL+oyu4=
 k8s.io/apiserver v0.25.0/go.mod h1:BKwsE+PTC+aZK+6OJQDPr0v6uS91/HWxX7evElAH6xo=
-k8s.io/cli-runtime v0.25.0 h1:XBnTc2Fi+w818jcJGzhiJKQuXl8479sZ4FhtV5hVJ1Q=
-k8s.io/cli-runtime v0.25.0/go.mod h1:bHOI5ZZInRHhbq12OdUiYZQN8ml8aKZLwQgt9QlLINw=
+k8s.io/cli-runtime v0.25.2 h1:XOx+SKRjBpYMLY/J292BHTkmyDffl/qOx3YSuFZkTuc=
+k8s.io/cli-runtime v0.25.2/go.mod h1:OQx3+/0st6x5YpkkJQlEWLC73V0wHsOFMC1/roxV8Oc=
 k8s.io/client-go v0.25.2 h1:SUPp9p5CwM0yXGQrwYurw9LWz+YtMwhWd0GqOsSiefo=
 k8s.io/client-go v0.25.2/go.mod h1:i7cNU7N+yGQmJkewcRD2+Vuj4iz7b30kI8OcL3horQ4=
 k8s.io/component-base v0.25.2 h1:Nve/ZyHLUBHz1rqwkjXm/Re6IniNa5k7KgzxZpTfSQY=
diff --git a/internal/reconcile/summarize/summary_test.go b/internal/reconcile/summarize/summary_test.go
index 67af44c80..18de95f43 100644
--- a/internal/reconcile/summarize/summary_test.go
+++ b/internal/reconcile/summarize/summary_test.go
@@ -23,7 +23,6 @@ import (
 	"testing"
 	"time"
 
-	"github.com/darkowlzz/controller-check/status"
 	. "github.com/onsi/gomega"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
@@ -34,6 +33,7 @@ import (
 
 	"github.com/fluxcd/pkg/apis/meta"
 	"github.com/fluxcd/pkg/runtime/conditions"
+	conditionscheck "github.com/fluxcd/pkg/runtime/conditions/check"
 	"github.com/fluxcd/pkg/runtime/patch"
 
 	sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
@@ -370,8 +370,8 @@ func TestSummarizeAndPatch(t *testing.T) {
 			}
 
 			// Check if the object status is valid as per kstatus.
-			condns := &status.Conditions{NegativePolarity: testReadyConditions.NegativePolarity}
-			checker := status.NewChecker(client, condns)
+			condns := &conditionscheck.Conditions{NegativePolarity: testReadyConditions.NegativePolarity}
+			checker := conditionscheck.NewChecker(client, condns)
 			checker.CheckErr(ctx, obj)
 		})
 	}
diff --git a/main.go b/main.go
index 621cea36c..ad99b9bcb 100644
--- a/main.go
+++ b/main.go
@@ -54,6 +54,7 @@ import (
 )
 
 const controllerName = "source-controller"
+const artifactMaxSizeDefault int64 = 50 << 20
 
 var (
 	scheme   = runtime.NewScheme()
@@ -101,6 +102,7 @@ func main() {
 		helmCachePurgeInterval   string
 		artifactRetentionTTL     time.Duration
 		artifactRetentionRecords int
+		artifactMaxSize          int64
 	)
 
 	flag.StringVar(&metricsAddr, "metrics-addr", envOrDefault("METRICS_ADDR", ":8080"),
@@ -139,6 +141,8 @@ func main() {
 		"The duration of time that artifacts will be kept in storage before being garbage collected.")
 	flag.IntVar(&artifactRetentionRecords, "artifact-retention-records", 2,
 		"The maximum number of artifacts to be kept in storage after a garbage collection.")
+	flag.Int64Var(&artifactMaxSize, "artifact-max-size", artifactMaxSizeDefault,
+		"The max allowed size in bytes of an artifact contents produced from sources. The limit can be disabled by setting the value to zero or a negative value.")
 
 	clientOptions.BindFlags(flag.CommandLine)
 	logOptions.BindFlags(flag.CommandLine)
@@ -202,7 +206,7 @@ func main() {
 	if storageAdvAddr == "" {
 		storageAdvAddr = determineAdvStorageAddr(storageAddr, setupLog)
 	}
-	storage := mustInitStorage(storagePath, storageAdvAddr, artifactRetentionTTL, artifactRetentionRecords, setupLog)
+	storage := mustInitStorage(storagePath, storageAdvAddr, artifactRetentionTTL, artifactRetentionRecords, artifactMaxSize, setupLog)
 
 	if err = managed.InitManagedTransport(); err != nil {
 		// Log the error, but don't exit so as to not block reconcilers that are healthy.
@@ -350,14 +354,14 @@ func startFileServer(path string, address string, l logr.Logger) {
 	}
 }
 
-func mustInitStorage(path string, storageAdvAddr string, artifactRetentionTTL time.Duration, artifactRetentionRecords int, l logr.Logger) *controllers.Storage {
+func mustInitStorage(path string, storageAdvAddr string, artifactRetentionTTL time.Duration, artifactRetentionRecords int, artifactMaxSize int64, l logr.Logger) *controllers.Storage {
 	if path == "" {
 		p, _ := os.Getwd()
 		path = filepath.Join(p, "bin")
 		os.MkdirAll(path, 0o700)
 	}
 
-	storage, err := controllers.NewStorage(path, storageAdvAddr, artifactRetentionTTL, artifactRetentionRecords)
+	storage, err := controllers.NewStorage(path, storageAdvAddr, artifactRetentionTTL, artifactRetentionRecords, artifactMaxSize)
 	if err != nil {
 		l.Error(err, "unable to initialise storage")
 		os.Exit(1)
diff --git a/tests/fuzz/gitrepository_fuzzer.go b/tests/fuzz/gitrepository_fuzzer.go
index 0c495930a..6e09de9e5 100644
--- a/tests/fuzz/gitrepository_fuzzer.go
+++ b/tests/fuzz/gitrepository_fuzzer.go
@@ -174,7 +174,7 @@ func startEnvServer(setupReconcilers func(manager.Manager)) *envtest.Environment
 		panic(err)
 	}
 	defer os.RemoveAll(tmpStoragePath)
-	storage, err = controllers.NewStorage(tmpStoragePath, "localhost:5050", time.Minute*1, 2)
+	storage, err = controllers.NewStorage(tmpStoragePath, "localhost:5050", time.Minute*1, 2, 0)
 	if err != nil {
 		panic(err)
 	}