Repository: operator-framework/operator-lifecycle-manager
Pull request #2216 (2021-06-28 12:59:48)

Emit CSV metric on startup
`csv_succeeded` metric is lost between pod restarts.
This is because this metric is only emitted when CSV.Status is changed.
<!--

Before making a PR, please read our contributing guidelines https://github.com/operator-framework/operator-lifecycle-manager/blob/master/CONTRIBUTING.md

Note: Make sure your branch is rebased to the latest upstream master.

-->

**Description of the change:**
Emit `csv_succeeded`/`csv_abnormal` metric during OLM startup

**Motivation for the change:**
csv_succeeded metric is lost between pod restarts.

**Reviewer Checklist**
- [ ] Implementation matches the proposed design, or proposal is updated to match implementation
- [ ] Sufficient unit test coverage
- [ ] Sufficient end-to-end test coverage
- [ ] Docs updated or added to `/doc`
- [ ] Commit messages sensible and descriptive


<!--

Note: If this PR is fixing an issue make sure to add a note saying:
Closes #<ISSUE_NUMBER>

-->


openshift-ci[bot]: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/operator-framework/operator-lifecycle-manager/pull/2216#" title="Author self-approved">josefkarasek</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **kevinrizza** after the PR has been reviewed.
You can assign the PR to them by writing `/assign @kevinrizza` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=operator-framework%2Foperator-lifecycle-manager).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/operator-framework/operator-lifecycle-manager/blob/master/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["kevinrizza"]} -->
openshift-ci[bot]: Hi @josefkarasek. Thanks for your PR.

I'm waiting for a [operator-framework](https://github.com/orgs/operator-framework/people) member to verify that this patch is reasonable to test. If it is, they should reply with `/ok-to-test` on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should [join the org](https://github.com/orgs/operator-framework/people) to skip this step.

Once the patch is verified, the new status will be reflected by the `ok-to-test` label.

I understand the commands that are listed [here](https://go.k8s.io/bot-commands?repo=operator-framework%2Foperator-lifecycle-manager).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>

openshift-ci[bot]: @josefkarasek: This pull request references [Bugzilla bug 1952576](https://bugzilla.redhat.com/show_bug.cgi?id=1952576), which is valid. The bug has been updated to refer to the pull request using the external bug tracker.

<details><summary>3 validation(s) were run on this bug</summary>

* bug is open, matching expected state (open)
* bug target release (4.9.0) matches configured target release for branch (4.9.0)
* bug is in the state POST, which is one of the valid states (NEW, ASSIGNED, ON_DEV, POST, POST)</details>

Requesting review from QA contact:
/cc @jianzhangbjz

<details>

In response to [this](https://github.com/operator-framework/operator-lifecycle-manager/pull/2216):

>Bug 1952576: Emit CSV metric on startup


Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>
openshift-ci[bot]: @openshift-ci[bot]: GitHub didn't allow me to request PR reviews from the following users: jianzhangbjz.

Note that only [operator-framework members](https://github.com/orgs/operator-framework/people) and repo collaborators can review this PR, and authors cannot review their own PRs.

<details>

In response to [this](https://github.com/operator-framework/operator-lifecycle-manager/pull/2216#issuecomment-869662656):

>@josefkarasek: This pull request references [Bugzilla bug 1952576](https://bugzilla.redhat.com/show_bug.cgi?id=1952576), which is valid. The bug has been updated to refer to the pull request using the external bug tracker.
>
><details><summary>3 validation(s) were run on this bug</summary>
>
>* bug is open, matching expected state (open)
>* bug target release (4.9.0) matches configured target release for branch (4.9.0)
>* bug is in the state POST, which is one of the valid states (NEW, ASSIGNED, ON_DEV, POST, POST)</details>
>
>Requesting review from QA contact:
>/cc @jianzhangbjz
>
><details>
>
>In response to [this](https://github.com/operator-framework/operator-lifecycle-manager/pull/2216):
>
>>Bug 1952576: Emit CSV metric on startup
>
>
>Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
></details>


Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>
josefkarasek: This is an alternative solution along with #2213
openshift-ci[bot]: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/operator-framework/operator-lifecycle-manager/pull/2216#pullrequestreview-704192360" title="LGTM">anik120</a>*, *<a href="https://github.com/operator-framework/operator-lifecycle-manager/pull/2216#" title="Author self-approved">josefkarasek</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **kevinrizza** after the PR has been reviewed.
You can assign the PR to them by writing `/assign @kevinrizza` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=operator-framework%2Foperator-lifecycle-manager).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/operator-framework/operator-lifecycle-manager/blob/master/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["kevinrizza"]} -->
openshift-ci[bot]: New changes are detected. LGTM label has been removed.
openshift-ci[bot]: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/operator-framework/operator-lifecycle-manager/pull/2216#pullrequestreview-704192360" title="LGTM">anik120</a>*, *<a href="https://github.com/operator-framework/operator-lifecycle-manager/pull/2216#pullrequestreview-704303879" title="LGTM">dinhxuanvu</a>*, *<a href="https://github.com/operator-framework/operator-lifecycle-manager/pull/2216#" title="Author self-approved">josefkarasek</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **kevinrizza** after the PR has been reviewed.
You can assign the PR to them by writing `/assign @kevinrizza` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=operator-framework%2Foperator-lifecycle-manager).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/operator-framework/operator-lifecycle-manager/blob/master/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["kevinrizza"]} -->
openshift-ci[bot]: New changes are detected. LGTM label has been removed.
josefkarasek: /cc @ecordell
josefkarasek: /retest
openshift-ci[bot]: @josefkarasek: No Bugzilla bug is referenced in the title of this pull request.
To reference a bug, add 'Bug XXX:' to the title of this pull request and request another bug refresh with <code>/bugzilla refresh</code>.

<details>

In response to [this](https://github.com/operator-framework/operator-lifecycle-manager/pull/2216):

>Emit CSV metric on startup


Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>
kevinrizza: /approve
openshift-ci[bot]: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/operator-framework/operator-lifecycle-manager/pull/2216#pullrequestreview-704192360" title="LGTM">anik120</a>*, *<a href="https://github.com/operator-framework/operator-lifecycle-manager/pull/2216#pullrequestreview-704303879" title="LGTM">dinhxuanvu</a>*, *<a href="https://github.com/operator-framework/operator-lifecycle-manager/pull/2216#" title="Author self-approved">josefkarasek</a>*, *<a href="https://github.com/operator-framework/operator-lifecycle-manager/pull/2216#issuecomment-885041845" title="Approved">kevinrizza</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=operator-framework%2Foperator-lifecycle-manager).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/operator-framework/operator-lifecycle-manager/blob/master/OWNERS)~~ [kevinrizza]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
estroz: I don't see necessarily a "better" place for the EnsureCSVMetrics call since queueinformer.Operator.Run can't easily be wrapped, so this looks fine to me.

/lgtm
openshift-ci[bot]: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/operator-framework/operator-lifecycle-manager/pull/2216#pullrequestreview-704303879" title="LGTM">dinhxuanvu</a>*, *<a href="https://github.com/operator-framework/operator-lifecycle-manager/pull/2216#" title="Author self-approved">josefkarasek</a>*, *<a href="https://github.com/operator-framework/operator-lifecycle-manager/pull/2216#issuecomment-885041845" title="Approved">kevinrizza</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=operator-framework%2Foperator-lifecycle-manager).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/operator-framework/operator-lifecycle-manager/blob/master/OWNERS)~~ [dinhxuanvu,kevinrizza]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
openshift-ci[bot]: New changes are detected. LGTM label has been removed.
anik120: This looks good to me. If Evan's concerns have been assuaged (I don't see any conversation here that suggests that they have been yet), we can remove the hold and lgtm it.  
josefkarasek: /retest
josefkarasek: /retest
josefkarasek: /cc @anik120 

Patch:
diff --git a/cmd/olm/main.go b/cmd/olm/main.go
index f9bec9f68..72e2a4b42 100644
--- a/cmd/olm/main.go
+++ b/cmd/olm/main.go
@@ -171,6 +171,11 @@ func main() {
 	op.Run(ctx)
 	<-op.Ready()
 
+	// Emit CSV metric
+	if err = op.EnsureCSVMetric(); err != nil {
+		logger.WithError(err).Fatalf("error emitting metrics for existing CSV")
+	}
+
 	if *writeStatusName != "" {
 		reconciler, err := openshift.NewClusterOperatorReconciler(
 			openshift.WithClient(mgr.GetClient()),
diff --git a/go.mod b/go.mod
index 326b31185..39d01b89c 100644
--- a/go.mod
+++ b/go.mod
@@ -10,6 +10,7 @@ require (
 	github.com/distribution/distribution v2.7.1+incompatible
 	github.com/fsnotify/fsnotify v1.4.9
 	github.com/ghodss/yaml v1.0.0
+	github.com/go-air/gini v1.0.4
 	github.com/go-bindata/go-bindata/v3 v3.1.3
 	github.com/go-logr/logr v0.4.0
 	github.com/golang/mock v1.4.1
@@ -25,7 +26,6 @@ require (
 	github.com/openshift/api v0.0.0-20200331152225-585af27e34fd
 	github.com/openshift/client-go v0.0.0-20200326155132-2a6cd50aedd0
 	github.com/operator-framework/api v0.10.3
-	github.com/operator-framework/gini v1.1.0
 	github.com/operator-framework/operator-registry v1.17.5
 	github.com/otiai10/copy v1.2.0
 	github.com/pkg/errors v0.9.1
@@ -64,7 +64,4 @@ replace (
 	// Patch for a race condition involving metadata-only
 	// informers until it can be resolved upstream:
 	sigs.k8s.io/controller-runtime v0.9.2 => github.com/benluddy/controller-runtime v0.9.3-0.20210720171926-9bcb99bd9bd3
-
-	// pinned because no tag supports 1.18 yet
-	sigs.k8s.io/structured-merge-diff => sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06
 )
diff --git a/go.sum b/go.sum
index d862aed78..ef1e00aa9 100644
--- a/go.sum
+++ b/go.sum
@@ -350,6 +350,8 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME
 github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
 github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
 github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
+github.com/go-air/gini v1.0.4 h1:lteMAxHKNOAjIqazL/klOJJmxq6YxxSuJ17MnMXny+s=
+github.com/go-air/gini v1.0.4/go.mod h1:dd8RvT1xcv6N1da33okvBd8DhMh1/A4siGy6ErjTljs=
 github.com/go-bindata/go-bindata/v3 v3.1.3 h1:F0nVttLC3ws0ojc7p60veTurcOm//D4QBODNM7EGrCI=
 github.com/go-bindata/go-bindata/v3 v3.1.3/go.mod h1:1/zrpXsLD8YDIbhZRqXzm1Ghc7NhEvIN9+Z6R5/xH4I=
 github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
@@ -878,8 +880,6 @@ github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnh
 github.com/operator-framework/api v0.7.1/go.mod h1:L7IvLd/ckxJEJg/t4oTTlnHKAJIP/p51AvEslW3wYdY=
 github.com/operator-framework/api v0.10.3 h1:C4DE7Rr3+ztUw3mKiFyfAiUJSVOty/cJmpwE90/kYro=
 github.com/operator-framework/api v0.10.3/go.mod h1:tV0BUNvly7szq28ZPBXhjp1Sqg5yHCOeX19ui9K4vjI=
-github.com/operator-framework/gini v1.1.0 h1:w84bE/pi0nlnIRAxzYguoYAnL9s5T6RSVZKYWcxO3yI=
-github.com/operator-framework/gini v1.1.0/go.mod h1:L4GlF3xPPfQUoWglKhaBcR8/LZn3fWtMB1mwUyOGv98=
 github.com/operator-framework/operator-registry v1.17.5 h1:LR8m1rFz5Gcyje8WK6iYt+gIhtzqo52zMRALdmTYHT0=
 github.com/operator-framework/operator-registry v1.17.5/go.mod h1:sRQIgDMZZdUcmHltzyCnM6RUoDF+WS8Arj1BQIARDS8=
 github.com/otiai10/copy v1.2.0 h1:HvG945u96iNadPoG2/Ja2+AUJeW5YuFQMixq9yirC+k=
diff --git a/pkg/controller/operators/catalog/operator.go b/pkg/controller/operators/catalog/operator.go
index cc1b6922e..e83e77c28 100644
--- a/pkg/controller/operators/catalog/operator.go
+++ b/pkg/controller/operators/catalog/operator.go
@@ -55,6 +55,7 @@ import (
 	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/grpc"
 	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/reconciler"
 	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver"
+	resolvercache "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache"
 	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/solver"
 	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/catalogsource"
 	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/clients"
@@ -463,7 +464,7 @@ func (o *Operator) syncSourceState(state grpc.SourceState) {
 
 	switch state.State {
 	case connectivity.Ready:
-		o.resolver.Expire(state.Key)
+		o.resolver.Expire(resolvercache.SourceKey(state.Key))
 		if o.namespace == state.Key.Namespace {
 			namespaces, err := index.CatalogSubscriberNamespaces(o.catalogSubscriberIndexer,
 				state.Key.Name, state.Key.Namespace)
@@ -961,23 +962,30 @@ func (o *Operator) syncResolvingNamespace(obj interface{}) error {
 		// not-satisfiable error
 		if _, ok := err.(solver.NotSatisfiable); ok {
 			logger.WithError(err).Debug("resolution failed")
-			updateErr := o.setSubsCond(subs, v1alpha1.SubscriptionResolutionFailed, "ConstraintsNotSatisfiable", err.Error(), true)
+			subs = o.setSubsCond(subs, v1alpha1.SubscriptionResolutionFailed, "ConstraintsNotSatisfiable", err.Error(), true)
+			_, updateErr := o.updateSubscriptionStatuses(subs)
 			if updateErr != nil {
 				logger.WithError(updateErr).Debug("failed to update subs conditions")
+				return updateErr
 			}
 			return nil
 		}
-		updateErr := o.setSubsCond(subs, v1alpha1.SubscriptionResolutionFailed, "ErrorPreventedResolution", err.Error(), true)
+		subs = o.setSubsCond(subs, v1alpha1.SubscriptionResolutionFailed, "ErrorPreventedResolution", err.Error(), true)
+		_, updateErr := o.updateSubscriptionStatuses(subs)
 		if updateErr != nil {
 			logger.WithError(updateErr).Debug("failed to update subs conditions")
+			return updateErr
 		}
 		return err
-	} else {
-		updateErr := o.setSubsCond(subs, v1alpha1.SubscriptionResolutionFailed, "", "", false)
+	}
+
+	defer func() {
+		subs = o.setSubsCond(subs, v1alpha1.SubscriptionResolutionFailed, "", "", false)
+		_, updateErr := o.updateSubscriptionStatuses(subs)
 		if updateErr != nil {
-			logger.WithError(updateErr).Debug("failed to update subs conditions")
+			logger.WithError(updateErr).Warn("failed to update subscription conditions")
 		}
-	}
+	}()
 
 	// create installplan if anything updated
 	if len(updatedSubs) > 0 {
@@ -1008,9 +1016,13 @@ func (o *Operator) syncResolvingNamespace(obj interface{}) error {
 			logger.WithError(err).Debug("error ensuring installplan")
 			return err
 		}
-		if err := o.updateSubscriptionStatus(namespace, maxGeneration+1, updatedSubs, installPlanReference); err != nil {
-			logger.WithError(err).Debug("error ensuring subscription installplan state")
-			return err
+		updatedSubs = o.setIPReference(updatedSubs, maxGeneration+1, installPlanReference)
+		for _, updatedSub := range updatedSubs {
+			for i, sub := range subs {
+				if sub.Name == updatedSub.Name && sub.Namespace == updatedSub.Namespace {
+					subs[i] = updatedSub
+				}
+			}
 		}
 	} else {
 		logger.Debugf("no subscriptions were updated")
@@ -1128,12 +1140,8 @@ func (o *Operator) ensureSubscriptionCSVState(logger *logrus.Entry, sub *v1alpha
 	return updatedSub, true, nil
 }
 
-func (o *Operator) updateSubscriptionStatus(namespace string, gen int, subs []*v1alpha1.Subscription, installPlanRef *corev1.ObjectReference) error {
+func (o *Operator) setIPReference(subs []*v1alpha1.Subscription, gen int, installPlanRef *corev1.ObjectReference) []*v1alpha1.Subscription {
 	var (
-		errs        []error
-		mu          sync.Mutex
-		wg          sync.WaitGroup
-		getOpts     = metav1.GetOptions{}
 		lastUpdated = o.now()
 	)
 	for _, sub := range subs {
@@ -1144,33 +1152,8 @@ func (o *Operator) updateSubscriptionStatus(namespace string, gen int, subs []*v
 			sub.Status.State = v1alpha1.SubscriptionStateUpgradePending
 			sub.Status.InstallPlanGeneration = gen
 		}
-
-		wg.Add(1)
-		go func(s v1alpha1.Subscription) {
-			defer wg.Done()
-
-			update := func() error {
-				// Update the status of the latest revision
-				latest, err := o.client.OperatorsV1alpha1().Subscriptions(s.GetNamespace()).Get(context.TODO(), s.GetName(), getOpts)
-				if err != nil {
-					return err
-				}
-
-				latest.Status = s.Status
-				_, err = o.client.OperatorsV1alpha1().Subscriptions(namespace).UpdateStatus(context.TODO(), latest, metav1.UpdateOptions{})
-
-				return err
-			}
-			if err := retry.RetryOnConflict(retry.DefaultRetry, update); err != nil {
-				mu.Lock()
-				defer mu.Unlock()
-				errs = append(errs, err)
-			}
-		}(*sub)
 	}
-	wg.Wait()
-
-	return utilerrors.NewAggregate(errs)
+	return subs
 }
 
 func (o *Operator) ensureInstallPlan(logger *logrus.Entry, namespace string, gen int, subs []*v1alpha1.Subscription, installPlanApproval v1alpha1.Approval, steps []*v1alpha1.Step, bundleLookups []v1alpha1.BundleLookup) (*corev1.ObjectReference, error) {
@@ -1254,13 +1237,8 @@ func (o *Operator) createInstallPlan(namespace string, gen int, subs []*v1alpha1
 	return reference.GetReference(res)
 }
 
-func (o *Operator) setSubsCond(subs []*v1alpha1.Subscription, condType v1alpha1.SubscriptionConditionType, reason, message string, setTrue bool) error {
+func (o *Operator) setSubsCond(subs []*v1alpha1.Subscription, condType v1alpha1.SubscriptionConditionType, reason, message string, setTrue bool) []*v1alpha1.Subscription {
 	var (
-		errs        []error
-		mu          sync.Mutex
-		wg          sync.WaitGroup
-		getOpts     = metav1.GetOptions{}
-		updateOpts  = metav1.UpdateOptions{}
 		lastUpdated = o.now()
 	)
 	for _, sub := range subs {
@@ -1274,21 +1252,33 @@ func (o *Operator) setSubsCond(subs []*v1alpha1.Subscription, condType v1alpha1.
 			cond.Status = corev1.ConditionFalse
 		}
 		sub.Status.SetCondition(cond)
+	}
+	return subs
+}
+
+func (o *Operator) updateSubscriptionStatuses(subs []*v1alpha1.Subscription) ([]*v1alpha1.Subscription, error) {
+	var (
+		errs       []error
+		mu         sync.Mutex
+		wg         sync.WaitGroup
+		getOpts    = metav1.GetOptions{}
+		updateOpts = metav1.UpdateOptions{}
+	)
 
+	for _, sub := range subs {
 		wg.Add(1)
-		go func(s v1alpha1.Subscription) {
+		go func(sub *v1alpha1.Subscription) {
 			defer wg.Done()
 
 			update := func() error {
 				// Update the status of the latest revision
-				latest, err := o.client.OperatorsV1alpha1().Subscriptions(s.GetNamespace()).Get(context.TODO(), s.GetName(), getOpts)
+				latest, err := o.client.OperatorsV1alpha1().Subscriptions(sub.GetNamespace()).Get(context.TODO(), sub.GetName(), getOpts)
 				if err != nil {
 					return err
 				}
-
-				latest.Status = s.Status
-				_, err = o.client.OperatorsV1alpha1().Subscriptions(s.Namespace).UpdateStatus(context.TODO(), latest, updateOpts)
-
+				latest.Status = sub.Status
+				*sub = *latest
+				_, err = o.client.OperatorsV1alpha1().Subscriptions(sub.Namespace).UpdateStatus(context.TODO(), latest, updateOpts)
 				return err
 			}
 			if err := retry.RetryOnConflict(retry.DefaultRetry, update); err != nil {
@@ -1296,11 +1286,10 @@ func (o *Operator) setSubsCond(subs []*v1alpha1.Subscription, condType v1alpha1.
 				defer mu.Unlock()
 				errs = append(errs, err)
 			}
-		}(*sub)
+		}(sub)
 	}
 	wg.Wait()
-
-	return utilerrors.NewAggregate(errs)
+	return subs, utilerrors.NewAggregate(errs)
 }
 
 type UnpackedBundleReference struct {
diff --git a/pkg/controller/operators/olm/operator.go b/pkg/controller/operators/olm/operator.go
index 39018ee37..b2e6e8066 100644
--- a/pkg/controller/operators/olm/operator.go
+++ b/pkg/controller/operators/olm/operator.go
@@ -655,6 +655,23 @@ func (a *Operator) RegisterCSVWatchNotification(csvNotification csvutility.Watch
 	a.csvNotification = csvNotification
 }
 
+func (a *Operator) EnsureCSVMetric() error {
+	csvs, err := a.lister.OperatorsV1alpha1().ClusterServiceVersionLister().List(labels.Everything())
+	if err != nil {
+		return err
+	}
+	for _, csv := range csvs {
+		logger := a.logger.WithFields(logrus.Fields{
+			"name":      csv.GetName(),
+			"namespace": csv.GetNamespace(),
+			"self":      csv.GetSelfLink(),
+		})
+		logger.Debug("emitting metrics for existing CSV")
+		metrics.EmitCSVMetric(csv, csv)
+	}
+	return nil
+}
+
 func (a *Operator) syncGCObject(obj interface{}) (syncError error) {
 	metaObj, ok := obj.(metav1.Object)
 	if !ok {
diff --git a/pkg/controller/registry/resolver/cache/cache.go b/pkg/controller/registry/resolver/cache/cache.go
index c26f30d43..10ca6d07d 100644
--- a/pkg/controller/registry/resolver/cache/cache.go
+++ b/pkg/controller/registry/resolver/cache/cache.go
@@ -2,88 +2,140 @@ package cache
 
 import (
 	"context"
-	"encoding/json"
 	"fmt"
+	"io"
 	"sort"
 	"sync"
 	"time"
 
-	"k8s.io/apimachinery/pkg/util/errors"
-
 	"github.com/sirupsen/logrus"
+	"k8s.io/apimachinery/pkg/util/errors"
 
 	"github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/listers/operators/v1alpha1"
-	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry"
-	"github.com/operator-framework/operator-registry/pkg/api"
-	"github.com/operator-framework/operator-registry/pkg/client"
-	opregistry "github.com/operator-framework/operator-registry/pkg/registry"
+	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorlister"
 )
 
-type RegistryClientProvider interface {
-	ClientsForNamespaces(namespaces ...string) map[registry.CatalogKey]client.Interface
+const existingOperatorKey = "@existing"
+
+type SourceKey struct {
+	Name      string
+	Namespace string
 }
 
-type DefaultRegistryClientProvider struct {
-	logger logrus.FieldLogger
-	s      RegistryClientProvider
+func (k *SourceKey) String() string {
+	return fmt.Sprintf("%s/%s", k.Name, k.Namespace)
 }
 
-func NewDefaultRegistryClientProvider(log logrus.FieldLogger, store RegistryClientProvider) *DefaultRegistryClientProvider {
-	return &DefaultRegistryClientProvider{
-		logger: log,
-		s:      store,
+func (k *SourceKey) Empty() bool {
+	return k.Name == "" && k.Namespace == ""
+}
+
+func (k *SourceKey) Equal(compare SourceKey) bool {
+	return k.Name == compare.Name && k.Namespace == compare.Namespace
+}
+
+// Virtual indicates if this is a "virtual" catalog representing the currently installed operators in a namespace
+func (k *SourceKey) Virtual() bool {
+	return k.Name == existingOperatorKey && k.Namespace != ""
+}
+
+func NewVirtualSourceKey(namespace string) SourceKey {
+	return SourceKey{
+		Name:      existingOperatorKey,
+		Namespace: namespace,
 	}
 }
 
-func (rcp *DefaultRegistryClientProvider) ClientsForNamespaces(namespaces ...string) map[registry.CatalogKey]client.Interface {
-	return rcp.s.ClientsForNamespaces(namespaces...)
+type Source interface {
+	Snapshot(context.Context) (*Snapshot, error)
+}
+
+type SourceProvider interface {
+	// TODO: namespaces parameter is an artifact of SourceStore
+	Sources(namespaces ...string) map[SourceKey]Source
+}
+
+type StaticSourceProvider map[SourceKey]Source
+
+func (p StaticSourceProvider) Sources(namespaces ...string) map[SourceKey]Source {
+	result := make(map[SourceKey]Source)
+	for key, source := range p {
+		for _, namespace := range namespaces {
+			if key.Namespace == namespace {
+				result[key] = source
+				break
+			}
+		}
+	}
+	return result
 }
 
 type OperatorCacheProvider interface {
 	Namespaced(namespaces ...string) MultiCatalogOperatorFinder
-	Expire(catalog registry.CatalogKey)
+	Expire(catalog SourceKey)
 }
 
-type OperatorCache struct {
-	logger       logrus.FieldLogger
-	rcp          RegistryClientProvider
+type Cache struct {
+	logger       logrus.StdLogger
+	sp           SourceProvider
 	catsrcLister v1alpha1.CatalogSourceLister
-	snapshots    map[registry.CatalogKey]*CatalogSnapshot
+	snapshots    map[SourceKey]*snapshotHeader
 	ttl          time.Duration
 	sem          chan struct{}
 	m            sync.RWMutex
 }
 
-const defaultCatalogSourcePriority int = 0
-
 type catalogSourcePriority int
 
-var _ OperatorCacheProvider = &OperatorCache{}
+var _ OperatorCacheProvider = &Cache{}
+
+type Option func(*Cache)
+
+func WithLogger(logger logrus.StdLogger) Option {
+	return func(c *Cache) {
+		c.logger = logger
+	}
+}
+
+func WithCatalogSourceLister(catalogSourceLister v1alpha1.CatalogSourceLister) Option {
+	return func(c *Cache) {
+		c.catsrcLister = catalogSourceLister
+	}
+}
 
-func NewOperatorCache(rcp RegistryClientProvider, log logrus.FieldLogger, catsrcLister v1alpha1.CatalogSourceLister) *OperatorCache {
+func New(sp SourceProvider, options ...Option) *Cache {
 	const (
 		MaxConcurrentSnapshotUpdates = 4
 	)
 
-	return &OperatorCache{
-		logger:       log,
-		rcp:          rcp,
-		catsrcLister: catsrcLister,
-		snapshots:    make(map[registry.CatalogKey]*CatalogSnapshot),
+	cache := Cache{
+		logger: func() logrus.StdLogger {
+			logger := logrus.New()
+			logger.SetOutput(io.Discard)
+			return logger
+		}(),
+		sp:           sp,
+		catsrcLister: operatorlister.NewLister().OperatorsV1alpha1().CatalogSourceLister(),
+		snapshots:    make(map[SourceKey]*snapshotHeader),
 		ttl:          5 * time.Minute,
 		sem:          make(chan struct{}, MaxConcurrentSnapshotUpdates),
 	}
+
+	for _, opt := range options {
+		opt(&cache)
+	}
+
+	return &cache
 }
 
 type NamespacedOperatorCache struct {
-	Namespaces []string
-	existing   *registry.CatalogKey
-	Snapshots  map[registry.CatalogKey]*CatalogSnapshot
+	existing  *SourceKey
+	snapshots map[SourceKey]*snapshotHeader
 }
 
 func (c *NamespacedOperatorCache) Error() error {
 	var errs []error
-	for key, snapshot := range c.Snapshots {
+	for key, snapshot := range c.snapshots {
 		snapshot.m.Lock()
 		err := snapshot.err
 		snapshot.m.Unlock()
@@ -94,7 +146,7 @@ func (c *NamespacedOperatorCache) Error() error {
 	return errors.NewAggregate(errs)
 }
 
-func (c *OperatorCache) Expire(catalog registry.CatalogKey) {
+func (c *Cache) Expire(catalog SourceKey) {
 	c.m.Lock()
 	defer c.m.Unlock()
 	s, ok := c.snapshots[catalog]
@@ -104,31 +156,30 @@ func (c *OperatorCache) Expire(catalog registry.CatalogKey) {
 	s.expiry = time.Unix(0, 0)
 }
 
-func (c *OperatorCache) Namespaced(namespaces ...string) MultiCatalogOperatorFinder {
+func (c *Cache) Namespaced(namespaces ...string) MultiCatalogOperatorFinder {
 	const (
 		CachePopulateTimeout = time.Minute
 	)
 
 	now := time.Now()
-	clients := c.rcp.ClientsForNamespaces(namespaces...)
+	sources := c.sp.Sources(namespaces...)
 
 	result := NamespacedOperatorCache{
-		Namespaces: namespaces,
-		Snapshots:  make(map[registry.CatalogKey]*CatalogSnapshot),
+		snapshots: make(map[SourceKey]*snapshotHeader),
 	}
 
-	var misses []registry.CatalogKey
+	var misses []SourceKey
 	func() {
 		c.m.RLock()
 		defer c.m.RUnlock()
-		for key := range clients {
+		for key := range sources {
 			snapshot, ok := c.snapshots[key]
 			if ok {
 				func() {
 					snapshot.m.RLock()
 					defer snapshot.m.RUnlock()
-					if !snapshot.Expired(now) && snapshot.Operators != nil && len(snapshot.Operators) > 0 {
-						result.Snapshots[key] = snapshot
+					if snapshot.Valid(now) {
+						result.snapshots[key] = snapshot
 					} else {
 						misses = append(misses, key)
 					}
@@ -148,9 +199,9 @@ func (c *OperatorCache) Namespaced(namespaces ...string) MultiCatalogOperatorFin
 	defer c.m.Unlock()
 
 	// Take the opportunity to clear expired snapshots while holding the lock.
-	var expired []registry.CatalogKey
+	var expired []SourceKey
 	for key, snapshot := range c.snapshots {
-		if snapshot.Expired(now) {
+		if !snapshot.Valid(now) {
 			snapshot.Cancel()
 			expired = append(expired, key)
 		}
@@ -162,8 +213,8 @@ func (c *OperatorCache) Namespaced(namespaces ...string) MultiCatalogOperatorFin
 	// Check for any snapshots that were populated while waiting to acquire the lock.
 	var found int
 	for i := range misses {
-		if snapshot, ok := c.snapshots[misses[i]]; ok && !snapshot.Expired(now) && snapshot.Operators != nil && len(snapshot.Operators) > 0 {
-			result.Snapshots[misses[i]] = snapshot
+		if hdr, ok := c.snapshots[misses[i]]; ok && hdr.Valid(now) {
+			result.snapshots[misses[i]] = hdr
 			misses[found], misses[i] = misses[i], misses[found]
 			found++
 		}
@@ -173,120 +224,49 @@ func (c *OperatorCache) Namespaced(namespaces ...string) MultiCatalogOperatorFin
 	for _, miss := range misses {
 		ctx, cancel := context.WithTimeout(context.Background(), CachePopulateTimeout)
 
-		catsrcPriority := defaultCatalogSourcePriority
-		// Ignoring error and treat catsrc priority as 0 if not found.
-		catsrc, err := c.catsrcLister.CatalogSources(miss.Namespace).Get(miss.Name)
-		if err == nil {
-			catsrcPriority = catsrc.Spec.Priority
-		}
-
-		s := CatalogSnapshot{
-			logger:   c.logger.WithField("catalog", miss),
-			Key:      miss,
-			expiry:   now.Add(c.ttl),
-			pop:      cancel,
-			Priority: catalogSourcePriority(catsrcPriority),
+		hdr := snapshotHeader{
+			key:    miss,
+			expiry: now.Add(c.ttl),
+			pop:    cancel,
 		}
-		s.m.Lock()
-		c.snapshots[miss] = &s
-		result.Snapshots[miss] = &s
-		go c.populate(ctx, &s, clients[miss])
-	}
-
-	return &result
-}
 
-func (c *OperatorCache) populate(ctx context.Context, snapshot *CatalogSnapshot, registry client.Interface) {
-	defer snapshot.m.Unlock()
-	defer func() {
-		// Don't cache an errorred snapshot.
-		if snapshot.err != nil {
-			snapshot.expiry = time.Time{}
+		// Ignoring error and treat catsrc priority as 0 if not found.
+		if catsrc, _ := c.catsrcLister.CatalogSources(miss.Namespace).Get(miss.Name); catsrc != nil {
+			hdr.priority = catsrc.Spec.Priority
 		}
-	}()
 
-	c.sem <- struct{}{}
-	defer func() { <-c.sem }()
+		hdr.m.Lock()
+		c.snapshots[miss] = &hdr
+		result.snapshots[miss] = &hdr
 
-	// Fetching default channels this way makes many round trips
-	// -- may need to either add a new API to fetch all at once,
-	// or embed the information into Bundle.
-	defaultChannels := make(map[string]string)
-
-	it, err := registry.ListBundles(ctx)
-	if err != nil {
-		snapshot.logger.Errorf("failed to list bundles: %s", err.Error())
-		snapshot.err = err
-		return
+		go func(ctx context.Context, hdr *snapshotHeader, source Source) {
+			defer hdr.m.Unlock()
+			c.sem <- struct{}{}
+			defer func() { <-c.sem }()
+			hdr.snapshot, hdr.err = source.Snapshot(ctx)
+		}(ctx, &hdr, sources[miss])
 	}
-	c.logger.WithField("catalog", snapshot.Key.String()).Debug("updating cache")
-	var operators []*Operator
-	for b := it.Next(); b != nil; b = it.Next() {
-		defaultChannel, ok := defaultChannels[b.PackageName]
-		if !ok {
-			if p, err := registry.GetPackage(ctx, b.PackageName); err != nil {
-				snapshot.logger.Warnf("failed to retrieve default channel for bundle, continuing: %v", err)
-				continue
-			} else {
-				defaultChannels[b.PackageName] = p.DefaultChannelName
-				defaultChannel = p.DefaultChannelName
-			}
-		}
-		o, err := NewOperatorFromBundle(b, "", snapshot.Key, defaultChannel)
-		if err != nil {
-			snapshot.logger.Warnf("failed to construct operator from bundle, continuing: %v", err)
-			continue
-		}
-		o.ProvidedAPIs = o.ProvidedAPIs.StripPlural()
-		o.RequiredAPIs = o.RequiredAPIs.StripPlural()
-		o.Replaces = b.Replaces
-		EnsurePackageProperty(o, b.PackageName, b.Version)
-		operators = append(operators, o)
-	}
-	if err := it.Error(); err != nil {
-		snapshot.logger.Warnf("error encountered while listing bundles: %s", err.Error())
-		snapshot.err = err
-	}
-	snapshot.Operators = operators
-}
 
-func EnsurePackageProperty(o *Operator, name, version string) {
-	for _, p := range o.Properties {
-		if p.Type == opregistry.PackageType {
-			return
-		}
-	}
-	prop := opregistry.PackageProperty{
-		PackageName: name,
-		Version:     version,
-	}
-	bytes, err := json.Marshal(prop)
-	if err != nil {
-		return
-	}
-	o.Properties = append(o.Properties, &api.Property{
-		Type:  opregistry.PackageType,
-		Value: string(bytes),
-	})
+	return &result
 }
 
-func (c *NamespacedOperatorCache) Catalog(k registry.CatalogKey) OperatorFinder {
+func (c *NamespacedOperatorCache) Catalog(k SourceKey) OperatorFinder {
 	// all catalogs match the empty catalog
 	if k.Empty() {
 		return c
 	}
-	if snapshot, ok := c.Snapshots[k]; ok {
+	if snapshot, ok := c.snapshots[k]; ok {
 		return snapshot
 	}
 	return EmptyOperatorFinder{}
 }
 
-func (c *NamespacedOperatorCache) FindPreferred(preferred *registry.CatalogKey, p ...OperatorPredicate) []*Operator {
+func (c *NamespacedOperatorCache) FindPreferred(preferred *SourceKey, preferredNamespace string, p ...OperatorPredicate) []*Operator {
 	var result []*Operator
 	if preferred != nil && preferred.Empty() {
 		preferred = nil
 	}
-	sorted := NewSortableSnapshots(c.existing, preferred, c.Namespaces, c.Snapshots)
+	sorted := newSortableSnapshots(c.existing, preferred, preferredNamespace, c.snapshots)
 	sort.Sort(sorted)
 	for _, snapshot := range sorted.snapshots {
 		result = append(result, snapshot.Find(p...)...)
@@ -294,65 +274,71 @@ func (c *NamespacedOperatorCache) FindPreferred(preferred *registry.CatalogKey,
 	return result
 }
 
-func (c *NamespacedOperatorCache) WithExistingOperators(snapshot *CatalogSnapshot) MultiCatalogOperatorFinder {
+func (c *NamespacedOperatorCache) WithExistingOperators(snapshot *Snapshot, namespace string) MultiCatalogOperatorFinder {
+	key := NewVirtualSourceKey(namespace)
 	o := &NamespacedOperatorCache{
-		Namespaces: c.Namespaces,
-		existing:   &snapshot.Key,
-		Snapshots:  c.Snapshots,
+		existing: &key,
+		snapshots: map[SourceKey]*snapshotHeader{
+			key: {
+				key:      key,
+				snapshot: snapshot,
+			},
+		},
+	}
+	for k, v := range c.snapshots {
+		o.snapshots[k] = v
 	}
-	o.Snapshots[snapshot.Key] = snapshot
 	return o
 }
 
 func (c *NamespacedOperatorCache) Find(p ...OperatorPredicate) []*Operator {
-	return c.FindPreferred(nil, p...)
+	return c.FindPreferred(nil, "", p...)
+}
+
+type Snapshot struct {
+	Entries []*Operator
 }
 
-type CatalogSnapshot struct {
-	logger    logrus.FieldLogger
-	Key       registry.CatalogKey
-	expiry    time.Time
-	Operators []*Operator
-	m         sync.RWMutex
-	pop       context.CancelFunc
-	Priority  catalogSourcePriority
-	err       error
+var _ Source = &Snapshot{}
+
+func (s *Snapshot) Snapshot(context.Context) (*Snapshot, error) {
+	return s, nil
 }
 
-func (s *CatalogSnapshot) Cancel() {
-	s.pop()
+type snapshotHeader struct {
+	snapshot *Snapshot
+
+	key      SourceKey
+	expiry   time.Time
+	m        sync.RWMutex
+	pop      context.CancelFunc
+	err      error
+	priority int
 }
 
-func (s *CatalogSnapshot) Expired(at time.Time) bool {
-	return !at.Before(s.expiry)
+func (hdr *snapshotHeader) Cancel() {
+	hdr.pop()
 }
 
-// NewRunningOperatorSnapshot creates a CatalogSnapshot that represents a set of existing installed operators
-// in the cluster.
-func NewRunningOperatorSnapshot(logger logrus.FieldLogger, key registry.CatalogKey, o []*Operator) *CatalogSnapshot {
-	return &CatalogSnapshot{
-		logger:    logger,
-		Key:       key,
-		Operators: o,
-	}
+func (hdr *snapshotHeader) Valid(at time.Time) bool {
+	hdr.m.RLock()
+	defer hdr.m.RUnlock()
+	return hdr.snapshot != nil && hdr.err == nil && at.Before(hdr.expiry)
 }
 
-type SortableSnapshots struct {
-	snapshots  []*CatalogSnapshot
-	namespaces map[string]int
-	preferred  *registry.CatalogKey
-	existing   *registry.CatalogKey
+type sortableSnapshots struct {
+	snapshots          []*snapshotHeader
+	preferredNamespace string
+	preferred          *SourceKey
+	existing           *SourceKey
 }
 
-func NewSortableSnapshots(existing, preferred *registry.CatalogKey, namespaces []string, snapshots map[registry.CatalogKey]*CatalogSnapshot) SortableSnapshots {
-	sorted := SortableSnapshots{
-		existing:   existing,
-		preferred:  preferred,
-		snapshots:  make([]*CatalogSnapshot, 0),
-		namespaces: make(map[string]int, 0),
-	}
-	for i, n := range namespaces {
-		sorted.namespaces[n] = i
+func newSortableSnapshots(existing, preferred *SourceKey, preferredNamespace string, snapshots map[SourceKey]*snapshotHeader) sortableSnapshots {
+	sorted := sortableSnapshots{
+		existing:           existing,
+		preferred:          preferred,
+		snapshots:          make([]*snapshotHeader, 0),
+		preferredNamespace: preferredNamespace,
 	}
 	for _, s := range snapshots {
 		sorted.snapshots = append(sorted.snapshots, s)
@@ -360,60 +346,69 @@ func NewSortableSnapshots(existing, preferred *registry.CatalogKey, namespaces [
 	return sorted
 }
 
-var _ sort.Interface = SortableSnapshots{}
+var _ sort.Interface = sortableSnapshots{}
 
 // Len is the number of elements in the collection.
-func (s SortableSnapshots) Len() int {
+func (s sortableSnapshots) Len() int {
 	return len(s.snapshots)
 }
 
 // Less reports whether the element with
 // index i should sort before the element with index j.
-func (s SortableSnapshots) Less(i, j int) bool {
+func (s sortableSnapshots) Less(i, j int) bool {
 	// existing operators are preferred over catalog operators
 	if s.existing != nil &&
-		s.snapshots[i].Key.Name == s.existing.Name &&
-		s.snapshots[i].Key.Namespace == s.existing.Namespace {
+		s.snapshots[i].key.Name == s.existing.Name &&
+		s.snapshots[i].key.Namespace == s.existing.Namespace {
 		return true
 	}
 	if s.existing != nil &&
-		s.snapshots[j].Key.Name == s.existing.Name &&
-		s.snapshots[j].Key.Namespace == s.existing.Namespace {
+		s.snapshots[j].key.Name == s.existing.Name &&
+		s.snapshots[j].key.Namespace == s.existing.Namespace {
 		return false
 	}
 
 	// preferred catalog is less than all other catalogs
 	if s.preferred != nil &&
-		s.snapshots[i].Key.Name == s.preferred.Name &&
-		s.snapshots[i].Key.Namespace == s.preferred.Namespace {
+		s.snapshots[i].key.Name == s.preferred.Name &&
+		s.snapshots[i].key.Namespace == s.preferred.Namespace {
 		return true
 	}
 	if s.preferred != nil &&
-		s.snapshots[j].Key.Name == s.preferred.Name &&
-		s.snapshots[j].Key.Namespace == s.preferred.Namespace {
+		s.snapshots[j].key.Name == s.preferred.Name &&
+		s.snapshots[j].key.Namespace == s.preferred.Namespace {
 		return false
 	}
 
 	// the rest are sorted first on priority, namespace and then by name
-	if s.snapshots[i].Priority != s.snapshots[j].Priority {
-		return s.snapshots[i].Priority > s.snapshots[j].Priority
+	if s.snapshots[i].priority != s.snapshots[j].priority {
+		return s.snapshots[i].priority > s.snapshots[j].priority
 	}
-	if s.snapshots[i].Key.Namespace != s.snapshots[j].Key.Namespace {
-		return s.namespaces[s.snapshots[i].Key.Namespace] < s.namespaces[s.snapshots[j].Key.Namespace]
+
+	if s.snapshots[i].key.Namespace != s.snapshots[j].key.Namespace {
+		if s.snapshots[i].key.Namespace == s.preferredNamespace {
+			return true
+		}
+		if s.snapshots[j].key.Namespace == s.preferredNamespace {
+			return false
+		}
 	}
 
-	return s.snapshots[i].Key.Name < s.snapshots[j].Key.Name
+	return s.snapshots[i].key.Name < s.snapshots[j].key.Name
 }
 
 // Swap swaps the elements with indexes i and j.
-func (s SortableSnapshots) Swap(i, j int) {
+func (s sortableSnapshots) Swap(i, j int) {
 	s.snapshots[i], s.snapshots[j] = s.snapshots[j], s.snapshots[i]
 }
 
-func (s *CatalogSnapshot) Find(p ...OperatorPredicate) []*Operator {
+func (s *snapshotHeader) Find(p ...OperatorPredicate) []*Operator {
 	s.m.RLock()
 	defer s.m.RUnlock()
-	return Filter(s.Operators, p...)
+	if s.snapshot == nil {
+		return nil
+	}
+	return Filter(s.snapshot.Entries, p...)
 }
 
 type OperatorFinder interface {
@@ -421,9 +416,9 @@ type OperatorFinder interface {
 }
 
 type MultiCatalogOperatorFinder interface {
-	Catalog(registry.CatalogKey) OperatorFinder
-	FindPreferred(*registry.CatalogKey, ...OperatorPredicate) []*Operator
-	WithExistingOperators(*CatalogSnapshot) MultiCatalogOperatorFinder
+	Catalog(SourceKey) OperatorFinder
+	FindPreferred(preferred *SourceKey, preferredNamespace string, predicates ...OperatorPredicate) []*Operator
+	WithExistingOperators(snapshot *Snapshot, namespace string) MultiCatalogOperatorFinder
 	Error() error
 	OperatorFinder
 }
diff --git a/pkg/controller/registry/resolver/cache/operators.go b/pkg/controller/registry/resolver/cache/operators.go
index 1df76b270..1c4307793 100644
--- a/pkg/controller/registry/resolver/cache/operators.go
+++ b/pkg/controller/registry/resolver/cache/operators.go
@@ -11,7 +11,6 @@ import (
 	"k8s.io/apimachinery/pkg/runtime/schema"
 
 	"github.com/operator-framework/api/pkg/operators/v1alpha1"
-	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry"
 	"github.com/operator-framework/operator-registry/pkg/api"
 	opregistry "github.com/operator-framework/operator-registry/pkg/registry"
 )
@@ -194,7 +193,7 @@ type OperatorSourceInfo struct {
 	Package        string
 	Channel        string
 	StartingCSV    string
-	Catalog        registry.CatalogKey
+	Catalog        SourceKey
 	DefaultChannel bool
 	Subscription   *v1alpha1.Subscription
 }
@@ -203,7 +202,7 @@ func (i *OperatorSourceInfo) String() string {
 	return fmt.Sprintf("%s/%s in %s/%s", i.Package, i.Channel, i.Catalog.Name, i.Catalog.Namespace)
 }
 
-var NoCatalog = registry.CatalogKey{Name: "", Namespace: ""}
+var NoCatalog = SourceKey{Name: "", Namespace: ""}
 var ExistingOperator = OperatorSourceInfo{Package: "", Channel: "", StartingCSV: "", Catalog: NoCatalog, DefaultChannel: false}
 
 type Operator struct {
@@ -219,7 +218,7 @@ type Operator struct {
 	Properties   []*api.Property
 }
 
-func NewOperatorFromBundle(bundle *api.Bundle, startingCSV string, sourceKey registry.CatalogKey, defaultChannel string) (*Operator, error) {
+func NewOperatorFromBundle(bundle *api.Bundle, startingCSV string, sourceKey SourceKey, defaultChannel string) (*Operator, error) {
 	parsedVersion, err := semver.ParseTolerant(bundle.Version)
 	version := &parsedVersion
 	if err != nil {
diff --git a/pkg/controller/registry/resolver/cache/predicates.go b/pkg/controller/registry/resolver/cache/predicates.go
index 7e8649d5d..292dda480 100644
--- a/pkg/controller/registry/resolver/cache/predicates.go
+++ b/pkg/controller/registry/resolver/cache/predicates.go
@@ -7,7 +7,6 @@ import (
 
 	"github.com/blang/semver/v4"
 
-	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry"
 	"github.com/operator-framework/operator-registry/pkg/api"
 	opregistry "github.com/operator-framework/operator-registry/pkg/registry"
 )
@@ -140,10 +139,10 @@ func (l labelPredicate) String() string {
 }
 
 type catalogPredicate struct {
-	key registry.CatalogKey
+	key SourceKey
 }
 
-func CatalogPredicate(key registry.CatalogKey) OperatorPredicate {
+func CatalogPredicate(key SourceKey) OperatorPredicate {
 	return catalogPredicate{key: key}
 }
 
diff --git a/pkg/controller/registry/resolver/installabletypes.go b/pkg/controller/registry/resolver/installabletypes.go
index ba9cd602b..695c2b07a 100644
--- a/pkg/controller/registry/resolver/installabletypes.go
+++ b/pkg/controller/registry/resolver/installabletypes.go
@@ -4,7 +4,6 @@ import (
 	"fmt"
 	"strings"
 
-	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry"
 	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache"
 	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/solver"
 	operatorregistry "github.com/operator-framework/operator-registry/pkg/registry"
@@ -37,13 +36,13 @@ func (i *BundleInstallable) AddConstraint(c solver.Constraint) {
 	i.constraints = append(i.constraints, c)
 }
 
-func (i *BundleInstallable) BundleSourceInfo() (string, string, registry.CatalogKey, error) {
+func (i *BundleInstallable) BundleSourceInfo() (string, string, cache.SourceKey, error) {
 	info := strings.Split(i.identifier.String(), "/")
 	// This should be enforced by Kube naming constraints
 	if len(info) != 4 {
-		return "", "", registry.CatalogKey{}, fmt.Errorf("Unable to parse identifier %s for source info", i.identifier)
+		return "", "", cache.SourceKey{}, fmt.Errorf("Unable to parse identifier %s for source info", i.identifier)
 	}
-	catalog := registry.CatalogKey{
+	catalog := cache.SourceKey{
 		Name:      info[0],
 		Namespace: info[1],
 	}
@@ -52,7 +51,7 @@ func (i *BundleInstallable) BundleSourceInfo() (string, string, registry.Catalog
 	return csvName, channel, catalog, nil
 }
 
-func bundleId(bundle, channel string, catalog registry.CatalogKey) solver.Identifier {
+func bundleId(bundle, channel string, catalog cache.SourceKey) solver.Identifier {
 	return solver.IdentifierFromString(fmt.Sprintf("%s/%s/%s", catalog.String(), channel, bundle))
 }
 
diff --git a/pkg/controller/registry/resolver/instrumented_resolver.go b/pkg/controller/registry/resolver/instrumented_resolver.go
index d8af9350c..453ae4a0b 100644
--- a/pkg/controller/registry/resolver/instrumented_resolver.go
+++ b/pkg/controller/registry/resolver/instrumented_resolver.go
@@ -4,8 +4,7 @@ import (
 	"time"
 
 	"github.com/operator-framework/api/pkg/operators/v1alpha1"
-
-	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry"
+	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache"
 )
 
 type InstrumentedResolver struct {
@@ -35,6 +34,6 @@ func (ir *InstrumentedResolver) ResolveSteps(namespace string) ([]*v1alpha1.Step
 	return steps, lookups, subs, err
 }
 
-func (ir *InstrumentedResolver) Expire(key registry.CatalogKey) {
+func (ir *InstrumentedResolver) Expire(key cache.SourceKey) {
 	ir.resolver.Expire(key)
 }
diff --git a/pkg/controller/registry/resolver/resolver.go b/pkg/controller/registry/resolver/resolver.go
index 06d9c10c7..38e779acd 100644
--- a/pkg/controller/registry/resolver/resolver.go
+++ b/pkg/controller/registry/resolver/resolver.go
@@ -13,7 +13,6 @@ import (
 
 	"github.com/operator-framework/api/pkg/operators/v1alpha1"
 	v1alpha1listers "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/listers/operators/v1alpha1"
-	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry"
 	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache"
 	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/projection"
 	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/solver"
@@ -30,10 +29,10 @@ type SatResolver struct {
 	log   logrus.FieldLogger
 }
 
-func NewDefaultSatResolver(rcp cache.RegistryClientProvider, catsrcLister v1alpha1listers.CatalogSourceLister, log logrus.FieldLogger) *SatResolver {
+func NewDefaultSatResolver(rcp cache.SourceProvider, catsrcLister v1alpha1listers.CatalogSourceLister, logger logrus.FieldLogger) *SatResolver {
 	return &SatResolver{
-		cache: cache.NewOperatorCache(rcp, log, catsrcLister),
-		log:   log,
+		cache: cache.New(rcp, cache.WithLogger(logger), cache.WithCatalogSourceLister(catsrcLister)),
+		log:   logger,
 	}
 }
 
@@ -61,9 +60,9 @@ func (r *SatResolver) SolveOperators(namespaces []string, csvs []*v1alpha1.Clust
 	if err != nil {
 		return nil, err
 	}
-	namespacedCache := r.cache.Namespaced(namespaces...).WithExistingOperators(existingSnapshot)
+	namespacedCache := r.cache.Namespaced(namespaces...).WithExistingOperators(existingSnapshot, namespaces[0])
 
-	_, existingInstallables, err := r.getBundleInstallables(registry.NewVirtualCatalogKey(namespaces[0]), existingSnapshot.Find(), namespacedCache, visited)
+	_, existingInstallables, err := r.getBundleInstallables(namespaces[0], cache.Filter(existingSnapshot.Entries, cache.True()), namespacedCache, visited)
 	if err != nil {
 		return nil, err
 	}
@@ -177,7 +176,7 @@ func (r *SatResolver) getSubscriptionInstallables(sub *v1alpha1.Subscription, cu
 	var cachePredicates, channelPredicates []cache.OperatorPredicate
 	installables := make(map[solver.Identifier]solver.Installable, 0)
 
-	catalog := registry.CatalogKey{
+	catalog := cache.SourceKey{
 		Name:      sub.Spec.CatalogSource,
 		Namespace: sub.Spec.CatalogSourceNamespace,
 	}
@@ -259,7 +258,7 @@ func (r *SatResolver) getSubscriptionInstallables(sub *v1alpha1.Subscription, cu
 	for _, o := range cache.Filter(sortedBundles, channelPredicates...) {
 		predicates := append(cachePredicates, cache.CSVNamePredicate(o.Name))
 		stack := namespacedCache.Catalog(catalog).Find(predicates...)
-		id, installable, err := r.getBundleInstallables(catalog, stack, namespacedCache, visited)
+		id, installable, err := r.getBundleInstallables(sub.Namespace, stack, namespacedCache, visited)
 		if err != nil {
 			return nil, err
 		}
@@ -288,12 +287,12 @@ func (r *SatResolver) getSubscriptionInstallables(sub *v1alpha1.Subscription, cu
 			// safe to remove this conflict if properties
 			// annotations are made mandatory for
 			// resolution.
-			c.AddConflict(bundleId(current.Name, current.Channel(), registry.NewVirtualCatalogKey(sub.GetNamespace())))
+			c.AddConflict(bundleId(current.Name, current.Channel(), cache.NewVirtualSourceKey(sub.GetNamespace())))
 		}
 		depIds = append(depIds, c.Identifier())
 	}
 	if current != nil {
-		depIds = append(depIds, bundleId(current.Name, current.Channel(), registry.NewVirtualCatalogKey(sub.GetNamespace())))
+		depIds = append(depIds, bundleId(current.Name, current.Channel(), cache.NewVirtualSourceKey(sub.GetNamespace())))
 	}
 
 	// all candidates added as options for this constraint
@@ -303,7 +302,7 @@ func (r *SatResolver) getSubscriptionInstallables(sub *v1alpha1.Subscription, cu
 	return installables, nil
 }
 
-func (r *SatResolver) getBundleInstallables(catalog registry.CatalogKey, bundleStack []*cache.Operator, namespacedCache cache.MultiCatalogOperatorFinder, visited map[*cache.Operator]*BundleInstallable) (map[solver.Identifier]struct{}, map[solver.Identifier]*BundleInstallable, error) {
+func (r *SatResolver) getBundleInstallables(preferredNamespace string, bundleStack []*cache.Operator, namespacedCache cache.MultiCatalogOperatorFinder, visited map[*cache.Operator]*BundleInstallable) (map[solver.Identifier]struct{}, map[solver.Identifier]*BundleInstallable, error) {
 	errs := make([]error, 0)
 	installables := make(map[solver.Identifier]*BundleInstallable, 0) // all installables, including dependencies
 
@@ -370,7 +369,8 @@ func (r *SatResolver) getBundleInstallables(catalog registry.CatalogKey, bundleS
 					))
 				}
 			}
-			sortedBundles, err := r.sortBundles(namespacedCache.FindPreferred(&bundle.SourceInfo.Catalog, sourcePredicate))
+
+			sortedBundles, err := r.sortBundles(namespacedCache.FindPreferred(&bundle.SourceInfo.Catalog, preferredNamespace, sourcePredicate))
 			if err != nil {
 				errs = append(errs, err)
 				continue
@@ -422,7 +422,7 @@ func (r *SatResolver) inferProperties(csv *v1alpha1.ClusterServiceVersion, subs
 		// package against catalog contents, updates to the
 		// Subscription spec could result in a bad package
 		// inference.
-		for _, entry := range r.cache.Namespaced(sub.Namespace).Catalog(registry.CatalogKey{Namespace: sub.Spec.CatalogSourceNamespace, Name: sub.Spec.CatalogSource}).Find(cache.And(cache.CSVNamePredicate(csv.Name), cache.PkgPredicate(sub.Spec.Package))) {
+		for _, entry := range r.cache.Namespaced(sub.Spec.CatalogSourceNamespace).Catalog(cache.SourceKey{Namespace: sub.Spec.CatalogSourceNamespace, Name: sub.Spec.CatalogSource}).Find(cache.And(cache.CSVNamePredicate(csv.Name), cache.PkgPredicate(sub.Spec.Package))) {
 			if pkg := entry.Package(); pkg != "" {
 				packages[pkg] = struct{}{}
 			}
@@ -455,8 +455,8 @@ func (r *SatResolver) inferProperties(csv *v1alpha1.ClusterServiceVersion, subs
 	return properties, nil
 }
 
-func (r *SatResolver) newSnapshotForNamespace(namespace string, subs []*v1alpha1.Subscription, csvs []*v1alpha1.ClusterServiceVersion) (*cache.CatalogSnapshot, error) {
-	existingOperatorCatalog := registry.NewVirtualCatalogKey(namespace)
+func (r *SatResolver) newSnapshotForNamespace(namespace string, subs []*v1alpha1.Subscription, csvs []*v1alpha1.ClusterServiceVersion) (*cache.Snapshot, error) {
+	existingOperatorCatalog := cache.NewVirtualSourceKey(namespace)
 	// build a catalog snapshot of CSVs without subscriptions
 	csvSubscriptions := make(map[*v1alpha1.ClusterServiceVersion]*v1alpha1.Subscription)
 	for _, sub := range subs {
@@ -517,7 +517,7 @@ func (r *SatResolver) newSnapshotForNamespace(namespace string, subs []*v1alpha1
 		r.log.Infof("considered csvs without properties annotation during resolution: %v", names)
 	}
 
-	return cache.NewRunningOperatorSnapshot(r.log, existingOperatorCatalog, standaloneOperators), nil
+	return &cache.Snapshot{Entries: standaloneOperators}, nil
 }
 
 func (r *SatResolver) addInvariants(namespacedCache cache.MultiCatalogOperatorFinder, installables map[solver.Identifier]solver.Installable) {
@@ -579,17 +579,17 @@ func (r *SatResolver) addInvariants(namespacedCache cache.MultiCatalogOperatorFi
 
 func (r *SatResolver) sortBundles(bundles []*cache.Operator) ([]*cache.Operator, error) {
 	// assume bundles have been passed in sorted by catalog already
-	catalogOrder := make([]registry.CatalogKey, 0)
+	catalogOrder := make([]cache.SourceKey, 0)
 
 	type PackageChannel struct {
 		Package, Channel string
 		DefaultChannel   bool
 	}
 	// TODO: for now channels will be sorted lexicographically
-	channelOrder := make(map[registry.CatalogKey][]PackageChannel)
+	channelOrder := make(map[cache.SourceKey][]PackageChannel)
 
 	// partition by catalog -> channel -> bundle
-	partitionedBundles := map[registry.CatalogKey]map[PackageChannel][]*cache.Operator{}
+	partitionedBundles := map[cache.SourceKey]map[PackageChannel][]*cache.Operator{}
 	for _, b := range bundles {
 		pc := PackageChannel{
 			Package:        b.Package(),
diff --git a/pkg/controller/registry/resolver/solver/constraints.go b/pkg/controller/registry/resolver/solver/constraints.go
index ca3c4f533..785ee409f 100644
--- a/pkg/controller/registry/resolver/solver/constraints.go
+++ b/pkg/controller/registry/resolver/solver/constraints.go
@@ -4,8 +4,8 @@ import (
 	"fmt"
 	"strings"
 
-	"github.com/operator-framework/gini/logic"
-	"github.com/operator-framework/gini/z"
+	"github.com/go-air/gini/logic"
+	"github.com/go-air/gini/z"
 )
 
 // Constraint implementations limit the circumstances under which a
diff --git a/pkg/controller/registry/resolver/solver/lit_mapping.go b/pkg/controller/registry/resolver/solver/lit_mapping.go
index f837ffc3e..dedb5fb8d 100644
--- a/pkg/controller/registry/resolver/solver/lit_mapping.go
+++ b/pkg/controller/registry/resolver/solver/lit_mapping.go
@@ -4,9 +4,9 @@ import (
 	"fmt"
 	"strings"
 
-	"github.com/operator-framework/gini/inter"
-	"github.com/operator-framework/gini/logic"
-	"github.com/operator-framework/gini/z"
+	"github.com/go-air/gini/inter"
+	"github.com/go-air/gini/logic"
+	"github.com/go-air/gini/z"
 )
 
 type DuplicateIdentifier Identifier
diff --git a/pkg/controller/registry/resolver/solver/search.go b/pkg/controller/registry/resolver/solver/search.go
index f36678f1e..ed84ea930 100644
--- a/pkg/controller/registry/resolver/solver/search.go
+++ b/pkg/controller/registry/resolver/solver/search.go
@@ -3,8 +3,8 @@ package solver
 import (
 	"context"
 
-	"github.com/operator-framework/gini/inter"
-	"github.com/operator-framework/gini/z"
+	"github.com/go-air/gini/inter"
+	"github.com/go-air/gini/z"
 )
 
 type choice struct {
diff --git a/pkg/controller/registry/resolver/solver/solve.go b/pkg/controller/registry/resolver/solver/solve.go
index 712ae3bcc..5d81f459a 100644
--- a/pkg/controller/registry/resolver/solver/solve.go
+++ b/pkg/controller/registry/resolver/solver/solve.go
@@ -6,9 +6,9 @@ import (
 	"fmt"
 	"strings"
 
-	"github.com/operator-framework/gini"
-	"github.com/operator-framework/gini/inter"
-	"github.com/operator-framework/gini/z"
+	"github.com/go-air/gini"
+	"github.com/go-air/gini/inter"
+	"github.com/go-air/gini/z"
 )
 
 var Incomplete = errors.New("cancelled before a solution could be found")
diff --git a/pkg/controller/registry/resolver/source_registry.go b/pkg/controller/registry/resolver/source_registry.go
new file mode 100644
index 000000000..6c5c11998
--- /dev/null
+++ b/pkg/controller/registry/resolver/source_registry.go
@@ -0,0 +1,109 @@
+package resolver
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+
+	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry"
+	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache"
+	"github.com/operator-framework/operator-registry/pkg/api"
+	"github.com/operator-framework/operator-registry/pkg/client"
+	opregistry "github.com/operator-framework/operator-registry/pkg/registry"
+	"github.com/sirupsen/logrus"
+)
+
+type RegistryClientProvider interface {
+	ClientsForNamespaces(namespaces ...string) map[registry.CatalogKey]client.Interface
+}
+
+type registryClientAdapter struct {
+	rcp    RegistryClientProvider
+	logger logrus.StdLogger
+}
+
+func SourceProviderFromRegistryClientProvider(rcp RegistryClientProvider, logger logrus.StdLogger) cache.SourceProvider {
+	return &registryClientAdapter{
+		rcp:    rcp,
+		logger: logger,
+	}
+}
+
+type registrySource struct {
+	key    cache.SourceKey
+	client client.Interface
+	logger logrus.StdLogger
+}
+
+func (s *registrySource) Snapshot(ctx context.Context) (*cache.Snapshot, error) {
+	// Fetching default channels this way makes many round trips
+	// -- may need to either add a new API to fetch all at once,
+	// or embed the information into Bundle.
+	defaultChannels := make(map[string]string)
+
+	it, err := s.client.ListBundles(ctx)
+	if err != nil {
+		return nil, fmt.Errorf("failed to list bundles: %w", err)
+	}
+
+	var operators []*cache.Operator
+	for b := it.Next(); b != nil; b = it.Next() {
+		defaultChannel, ok := defaultChannels[b.PackageName]
+		if !ok {
+			if p, err := s.client.GetPackage(ctx, b.PackageName); err != nil {
+				s.logger.Printf("failed to retrieve default channel for bundle, continuing: %v", err)
+				continue
+			} else {
+				defaultChannels[b.PackageName] = p.DefaultChannelName
+				defaultChannel = p.DefaultChannelName
+			}
+		}
+		o, err := cache.NewOperatorFromBundle(b, "", s.key, defaultChannel)
+		if err != nil {
+			s.logger.Printf("failed to construct operator from bundle, continuing: %v", err)
+			continue
+		}
+		o.ProvidedAPIs = o.ProvidedAPIs.StripPlural()
+		o.RequiredAPIs = o.RequiredAPIs.StripPlural()
+		o.Replaces = b.Replaces
+		EnsurePackageProperty(o, b.PackageName, b.Version)
+		operators = append(operators, o)
+	}
+	if err := it.Error(); err != nil {
+		return nil, fmt.Errorf("error encountered while listing bundles: %w", err)
+	}
+
+	return &cache.Snapshot{Entries: operators}, nil
+}
+
+func (a *registryClientAdapter) Sources(namespaces ...string) map[cache.SourceKey]cache.Source {
+	result := make(map[cache.SourceKey]cache.Source)
+	for key, client := range a.rcp.ClientsForNamespaces(namespaces...) {
+		result[cache.SourceKey(key)] = &registrySource{
+			key:    cache.SourceKey(key),
+			client: client,
+			logger: a.logger,
+		}
+	}
+	return result
+}
+
+func EnsurePackageProperty(o *cache.Operator, name, version string) {
+	for _, p := range o.Properties {
+		if p.Type == opregistry.PackageType {
+			return
+		}
+	}
+	prop := opregistry.PackageProperty{
+		PackageName: name,
+		Version:     version,
+	}
+	bytes, err := json.Marshal(prop)
+	if err != nil {
+		return
+	}
+	o.Properties = append(o.Properties, &api.Property{
+		Type:  opregistry.PackageType,
+		Value: string(bytes),
+	})
+}
diff --git a/pkg/controller/registry/resolver/step_resolver.go b/pkg/controller/registry/resolver/step_resolver.go
index a696dfd65..7574dda57 100644
--- a/pkg/controller/registry/resolver/step_resolver.go
+++ b/pkg/controller/registry/resolver/step_resolver.go
@@ -17,7 +17,6 @@ import (
 	"github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned"
 	v1alpha1listers "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/listers/operators/v1alpha1"
 	controllerbundle "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/bundle"
-	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry"
 	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache"
 	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/projection"
 	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorlister"
@@ -31,7 +30,7 @@ var timeNow = func() metav1.Time { return metav1.NewTime(time.Now().UTC()) }
 
 type StepResolver interface {
 	ResolveSteps(namespace string) ([]*v1alpha1.Step, []v1alpha1.BundleLookup, []*v1alpha1.Subscription, error)
-	Expire(key registry.CatalogKey)
+	Expire(key cache.SourceKey)
 }
 
 type OperatorStepResolver struct {
@@ -48,7 +47,7 @@ type OperatorStepResolver struct {
 var _ StepResolver = &OperatorStepResolver{}
 
 func NewOperatorStepResolver(lister operatorlister.OperatorLister, client versioned.Interface, kubeclient kubernetes.Interface,
-	globalCatalogNamespace string, provider cache.RegistryClientProvider, log logrus.FieldLogger) *OperatorStepResolver {
+	globalCatalogNamespace string, provider RegistryClientProvider, log logrus.FieldLogger) *OperatorStepResolver {
 	return &OperatorStepResolver{
 		subLister:              lister.OperatorsV1alpha1().SubscriptionLister(),
 		csvLister:              lister.OperatorsV1alpha1().ClusterServiceVersionLister(),
@@ -56,12 +55,12 @@ func NewOperatorStepResolver(lister operatorlister.OperatorLister, client versio
 		client:                 client,
 		kubeclient:             kubeclient,
 		globalCatalogNamespace: globalCatalogNamespace,
-		satResolver:            NewDefaultSatResolver(cache.NewDefaultRegistryClientProvider(log, provider), lister.OperatorsV1alpha1().CatalogSourceLister(), log),
+		satResolver:            NewDefaultSatResolver(SourceProviderFromRegistryClientProvider(provider, log), lister.OperatorsV1alpha1().CatalogSourceLister(), log),
 		log:                    log,
 	}
 }
 
-func (r *OperatorStepResolver) Expire(key registry.CatalogKey) {
+func (r *OperatorStepResolver) Expire(key cache.SourceKey) {
 	r.satResolver.cache.Expire(key)
 }
 
@@ -109,7 +108,7 @@ func (r *OperatorStepResolver) ResolveSteps(namespace string) ([]*v1alpha1.Step,
 			if sub.Spec.Channel != "" && sub.Spec.Channel != sourceInfo.Channel {
 				continue
 			}
-			subCatalogKey := registry.CatalogKey{
+			subCatalogKey := cache.SourceKey{
 				Name:      sub.Spec.CatalogSource,
 				Namespace: sub.Spec.CatalogSourceNamespace,
 			}
diff --git a/pkg/fakes/fake_resolver.go b/pkg/fakes/fake_resolver.go
index 07ec94e0f..46f1ba9f8 100644
--- a/pkg/fakes/fake_resolver.go
+++ b/pkg/fakes/fake_resolver.go
@@ -5,15 +5,15 @@ import (
 	"sync"
 
 	"github.com/operator-framework/api/pkg/operators/v1alpha1"
-	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry"
 	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver"
+	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache"
 )
 
 type FakeStepResolver struct {
-	ExpireStub        func(registry.CatalogKey)
+	ExpireStub        func(cache.SourceKey)
 	expireMutex       sync.RWMutex
 	expireArgsForCall []struct {
-		arg1 registry.CatalogKey
+		arg1 cache.SourceKey
 	}
 	ResolveStepsStub        func(string) ([]*v1alpha1.Step, []v1alpha1.BundleLookup, []*v1alpha1.Subscription, error)
 	resolveStepsMutex       sync.RWMutex
@@ -36,10 +36,10 @@ type FakeStepResolver struct {
 	invocationsMutex sync.RWMutex
 }
 
-func (fake *FakeStepResolver) Expire(arg1 registry.CatalogKey) {
+func (fake *FakeStepResolver) Expire(arg1 cache.SourceKey) {
 	fake.expireMutex.Lock()
 	fake.expireArgsForCall = append(fake.expireArgsForCall, struct {
-		arg1 registry.CatalogKey
+		arg1 cache.SourceKey
 	}{arg1})
 	fake.recordInvocation("Expire", []interface{}{arg1})
 	fake.expireMutex.Unlock()
@@ -54,13 +54,13 @@ func (fake *FakeStepResolver) ExpireCallCount() int {
 	return len(fake.expireArgsForCall)
 }
 
-func (fake *FakeStepResolver) ExpireCalls(stub func(registry.CatalogKey)) {
+func (fake *FakeStepResolver) ExpireCalls(stub func(cache.SourceKey)) {
 	fake.expireMutex.Lock()
 	defer fake.expireMutex.Unlock()
 	fake.ExpireStub = stub
 }
 
-func (fake *FakeStepResolver) ExpireArgsForCall(i int) registry.CatalogKey {
+func (fake *FakeStepResolver) ExpireArgsForCall(i int) cache.SourceKey {
 	fake.expireMutex.RLock()
 	defer fake.expireMutex.RUnlock()
 	argsForCall := fake.expireArgsForCall[i]
diff --git a/scripts/install.sh b/scripts/install.sh
index 35c65b35c..8d592a9ed 100755
--- a/scripts/install.sh
+++ b/scripts/install.sh
@@ -14,7 +14,7 @@ if [[ ${#@} -lt 1 || ${#@} -gt 2 ]]; then
     exit 1
 fi
 
-if kubectl get deployment olm-operator -n openshift-operator-lifecycle-manager -o=jsonpath='{.spec}' > /dev/null 2>&1; then
+if kubectl get deployment olm-operator -n openshift-operator-lifecycle-manager > /dev/null 2>&1; then
     echo "OLM is already installed in a different configuration. This is common if you are not running a vanilla Kubernetes cluster. Exiting..."
     exit 1
 fi
@@ -24,6 +24,11 @@ base_url="${2:-${default_base_url}}"
 url="${base_url}/${release}"
 namespace=olm
 
+if kubectl get deployment olm-operator -n ${namespace} > /dev/null 2>&1; then
+    echo "OLM is already installed in ${namespace} namespace. Exiting..."
+    exit 1
+fi
+
 kubectl apply -f "${url}/crds.yaml"
 kubectl wait --for=condition=Established -f "${url}/crds.yaml"
 kubectl apply -f "${url}/olm.yaml"
diff --git a/vendor/github.com/operator-framework/gini/.travis.yml b/vendor/github.com/go-air/gini/.travis.yml
similarity index 100%
rename from vendor/github.com/operator-framework/gini/.travis.yml
rename to vendor/github.com/go-air/gini/.travis.yml
diff --git a/vendor/github.com/operator-framework/gini/LICENSE b/vendor/github.com/go-air/gini/LICENSE
similarity index 100%
rename from vendor/github.com/operator-framework/gini/LICENSE
rename to vendor/github.com/go-air/gini/LICENSE
diff --git a/vendor/github.com/go-air/gini/README.md b/vendor/github.com/go-air/gini/README.md
new file mode 100644
index 000000000..558b5c84b
--- /dev/null
+++ b/vendor/github.com/go-air/gini/README.md
@@ -0,0 +1,51 @@
+# ⊧ Gini: A fast SAT solver.
+
+The Gini sat solver is a fast, clean SAT solver written in Go. It is to our knowledge
+the first ever performant pure-Go SAT solver made available.
+
+
+[![GoDoc](https://godoc.org/github.com/go-air/gini?status.svg)](https://godoc.org/github.com/go-air/gini)
+
+[Google Group](https://groups.google.com/d/forum/ginisat) 
+
+This solver is fully open source, originally developped at IRI France.
+
+## Build/Install
+
+For the impatient:
+
+    go install github.com/go-air/gini/...@latest
+
+I recommend however building the package github.com/go-air/gini/internal/xo with bounds checking
+turned off.  This package is all about anything-goes performance and is the workhorse behind most of
+the gini sat solver.  It is also extensively tested and well benchmarked, so it should not pose any
+safety threat to client code.  This makes a signficant speed difference (maybe 10%) on long running
+problems.
+
+## The SAT problem in 5 minutes
+
+[The SAT Problem](docs/satprob.md)
+
+## Usage
+
+Our [user guide](docs/manual.md) shows how to solve SAT problems, circuits, do Boolean optimisation,
+use concurrency, using our distributed CRISP protocol, and more.
+
+
+## Citing Gini
+
+Zenodo DOI based citations and download:
+[![DOI](https://zenodo.org/badge/64034957.svg)](https://zenodo.org/badge/latestdoi/64034957)
+
+BibText:
+```
+@misc{scott_cotton_2019_2553490,
+  author       = {Scott  Cotton},
+  title        = {go-air/gini: Sapeur},
+  month        = jan,
+  year         = 2019,
+  doi          = {10.5281/zenodo.2553490},
+  url          = {https://doi.org/10.5281/zenodo.2553490}
+}
+```
+
diff --git a/vendor/github.com/operator-framework/gini/dimacs/cfilt.go b/vendor/github.com/go-air/gini/dimacs/cfilt.go
similarity index 100%
rename from vendor/github.com/operator-framework/gini/dimacs/cfilt.go
rename to vendor/github.com/go-air/gini/dimacs/cfilt.go
diff --git a/vendor/github.com/operator-framework/gini/dimacs/cnf.go b/vendor/github.com/go-air/gini/dimacs/cnf.go
similarity index 98%
rename from vendor/github.com/operator-framework/gini/dimacs/cnf.go
rename to vendor/github.com/go-air/gini/dimacs/cnf.go
index c00451f51..c426f7d3b 100644
--- a/vendor/github.com/operator-framework/gini/dimacs/cnf.go
+++ b/vendor/github.com/go-air/gini/dimacs/cnf.go
@@ -8,7 +8,7 @@ import (
 	"fmt"
 	"io"
 
-	"github.com/operator-framework/gini/z"
+	"github.com/go-air/gini/z"
 )
 
 // Type Reader holds info for reading dimacs formatted intput.
diff --git a/vendor/github.com/operator-framework/gini/dimacs/doc.go b/vendor/github.com/go-air/gini/dimacs/doc.go
similarity index 100%
rename from vendor/github.com/operator-framework/gini/dimacs/doc.go
rename to vendor/github.com/go-air/gini/dimacs/doc.go
diff --git a/vendor/github.com/operator-framework/gini/dimacs/icnf.go b/vendor/github.com/go-air/gini/dimacs/icnf.go
similarity index 97%
rename from vendor/github.com/operator-framework/gini/dimacs/icnf.go
rename to vendor/github.com/go-air/gini/dimacs/icnf.go
index 567762a1e..5f058d225 100644
--- a/vendor/github.com/operator-framework/gini/dimacs/icnf.go
+++ b/vendor/github.com/go-air/gini/dimacs/icnf.go
@@ -8,7 +8,7 @@ import (
 	"fmt"
 	"io"
 
-	"github.com/operator-framework/gini/z"
+	"github.com/go-air/gini/z"
 )
 
 type iCnfReader struct {
diff --git a/vendor/github.com/operator-framework/gini/dimacs/int.go b/vendor/github.com/go-air/gini/dimacs/int.go
similarity index 100%
rename from vendor/github.com/operator-framework/gini/dimacs/int.go
rename to vendor/github.com/go-air/gini/dimacs/int.go
diff --git a/vendor/github.com/operator-framework/gini/dimacs/lit.go b/vendor/github.com/go-air/gini/dimacs/lit.go
similarity index 89%
rename from vendor/github.com/operator-framework/gini/dimacs/lit.go
rename to vendor/github.com/go-air/gini/dimacs/lit.go
index 9ed081687..fdfd52441 100644
--- a/vendor/github.com/operator-framework/gini/dimacs/lit.go
+++ b/vendor/github.com/go-air/gini/dimacs/lit.go
@@ -6,7 +6,7 @@ package dimacs
 import (
 	"bufio"
 
-	"github.com/operator-framework/gini/z"
+	"github.com/go-air/gini/z"
 )
 
 func readLit(r *bufio.Reader) (m z.Lit, e error) {
diff --git a/vendor/github.com/operator-framework/gini/dimacs/solve.go b/vendor/github.com/go-air/gini/dimacs/solve.go
similarity index 100%
rename from vendor/github.com/operator-framework/gini/dimacs/solve.go
rename to vendor/github.com/go-air/gini/dimacs/solve.go
diff --git a/vendor/github.com/operator-framework/gini/dimacs/vis.go b/vendor/github.com/go-air/gini/dimacs/vis.go
similarity index 96%
rename from vendor/github.com/operator-framework/gini/dimacs/vis.go
rename to vendor/github.com/go-air/gini/dimacs/vis.go
index 9934b3703..7fc57be38 100644
--- a/vendor/github.com/operator-framework/gini/dimacs/vis.go
+++ b/vendor/github.com/go-air/gini/dimacs/vis.go
@@ -3,7 +3,7 @@
 
 package dimacs
 
-import "github.com/operator-framework/gini/z"
+import "github.com/go-air/gini/z"
 
 // Type Vis provides a visitor interface to reading dimacs files.
 //
diff --git a/vendor/github.com/operator-framework/gini/doc.go b/vendor/github.com/go-air/gini/doc.go
similarity index 100%
rename from vendor/github.com/operator-framework/gini/doc.go
rename to vendor/github.com/go-air/gini/doc.go
diff --git a/vendor/github.com/operator-framework/gini/gini.go b/vendor/github.com/go-air/gini/gini.go
similarity index 97%
rename from vendor/github.com/operator-framework/gini/gini.go
rename to vendor/github.com/go-air/gini/gini.go
index 52a6d4d05..5f32dc8e8 100644
--- a/vendor/github.com/operator-framework/gini/gini.go
+++ b/vendor/github.com/go-air/gini/gini.go
@@ -7,10 +7,10 @@ import (
 	"io"
 	"time"
 
-	"github.com/operator-framework/gini/dimacs"
-	"github.com/operator-framework/gini/inter"
-	"github.com/operator-framework/gini/internal/xo"
-	"github.com/operator-framework/gini/z"
+	"github.com/go-air/gini/dimacs"
+	"github.com/go-air/gini/inter"
+	"github.com/go-air/gini/internal/xo"
+	"github.com/go-air/gini/z"
 )
 
 // Gini is a concrete implementation of solver
diff --git a/vendor/github.com/go-air/gini/go.mod b/vendor/github.com/go-air/gini/go.mod
new file mode 100644
index 000000000..e5a95f859
--- /dev/null
+++ b/vendor/github.com/go-air/gini/go.mod
@@ -0,0 +1,3 @@
+module github.com/go-air/gini
+
+go 1.16
diff --git a/vendor/github.com/operator-framework/gini/go.sum b/vendor/github.com/go-air/gini/go.sum
similarity index 100%
rename from vendor/github.com/operator-framework/gini/go.sum
rename to vendor/github.com/go-air/gini/go.sum
diff --git a/vendor/github.com/operator-framework/gini/inter/doc.go b/vendor/github.com/go-air/gini/inter/doc.go
similarity index 100%
rename from vendor/github.com/operator-framework/gini/inter/doc.go
rename to vendor/github.com/go-air/gini/inter/doc.go
diff --git a/vendor/github.com/operator-framework/gini/inter/s.go b/vendor/github.com/go-air/gini/inter/s.go
similarity index 99%
rename from vendor/github.com/operator-framework/gini/inter/s.go
rename to vendor/github.com/go-air/gini/inter/s.go
index 835a93375..248068d11 100644
--- a/vendor/github.com/operator-framework/gini/inter/s.go
+++ b/vendor/github.com/go-air/gini/inter/s.go
@@ -6,7 +6,7 @@ package inter
 import (
 	"time"
 
-	"github.com/operator-framework/gini/z"
+	"github.com/go-air/gini/z"
 )
 
 // Interface Solveable encapsulates a decision
diff --git a/vendor/github.com/operator-framework/gini/inter/solve.go b/vendor/github.com/go-air/gini/inter/solve.go
similarity index 100%
rename from vendor/github.com/operator-framework/gini/inter/solve.go
rename to vendor/github.com/go-air/gini/inter/solve.go
diff --git a/vendor/github.com/operator-framework/gini/internal/xo/active.go b/vendor/github.com/go-air/gini/internal/xo/active.go
similarity index 98%
rename from vendor/github.com/operator-framework/gini/internal/xo/active.go
rename to vendor/github.com/go-air/gini/internal/xo/active.go
index 786f76204..b0c906db6 100644
--- a/vendor/github.com/operator-framework/gini/internal/xo/active.go
+++ b/vendor/github.com/go-air/gini/internal/xo/active.go
@@ -1,7 +1,7 @@
 package xo
 
 import (
-	"github.com/operator-framework/gini/z"
+	"github.com/go-air/gini/z"
 )
 
 type Active struct {
diff --git a/vendor/github.com/operator-framework/gini/internal/xo/cdat.go b/vendor/github.com/go-air/gini/internal/xo/cdat.go
similarity index 99%
rename from vendor/github.com/operator-framework/gini/internal/xo/cdat.go
rename to vendor/github.com/go-air/gini/internal/xo/cdat.go
index 41b8367f0..b97be0075 100644
--- a/vendor/github.com/operator-framework/gini/internal/xo/cdat.go
+++ b/vendor/github.com/go-air/gini/internal/xo/cdat.go
@@ -9,7 +9,7 @@ import (
 	"io"
 	"math"
 
-	"github.com/operator-framework/gini/z"
+	"github.com/go-air/gini/z"
 )
 
 // Type CDat: basic operations for storing all the literals (and Chds) in a CNF
diff --git a/vendor/github.com/operator-framework/gini/internal/xo/cdb.go b/vendor/github.com/go-air/gini/internal/xo/cdb.go
similarity index 99%
rename from vendor/github.com/operator-framework/gini/internal/xo/cdb.go
rename to vendor/github.com/go-air/gini/internal/xo/cdb.go
index d4615220b..98c062c75 100644
--- a/vendor/github.com/operator-framework/gini/internal/xo/cdb.go
+++ b/vendor/github.com/go-air/gini/internal/xo/cdb.go
@@ -8,7 +8,7 @@ import (
 	"fmt"
 	"io"
 
-	"github.com/operator-framework/gini/z"
+	"github.com/go-air/gini/z"
 )
 
 // Type Cdb is the main interface to clauses.
diff --git a/vendor/github.com/operator-framework/gini/internal/xo/cgc.go b/vendor/github.com/go-air/gini/internal/xo/cgc.go
similarity index 99%
rename from vendor/github.com/operator-framework/gini/internal/xo/cgc.go
rename to vendor/github.com/go-air/gini/internal/xo/cgc.go
index 94cc04e74..01c7d6451 100644
--- a/vendor/github.com/operator-framework/gini/internal/xo/cgc.go
+++ b/vendor/github.com/go-air/gini/internal/xo/cgc.go
@@ -6,7 +6,7 @@ package xo
 import (
 	"sort"
 
-	"github.com/operator-framework/gini/z"
+	"github.com/go-air/gini/z"
 )
 
 // Type Cgc encapsulates clause compaction/garbage collection.
diff --git a/vendor/github.com/operator-framework/gini/internal/xo/chd.go b/vendor/github.com/go-air/gini/internal/xo/chd.go
similarity index 100%
rename from vendor/github.com/operator-framework/gini/internal/xo/chd.go
rename to vendor/github.com/go-air/gini/internal/xo/chd.go
diff --git a/vendor/github.com/operator-framework/gini/internal/xo/cloc.go b/vendor/github.com/go-air/gini/internal/xo/cloc.go
similarity index 82%
rename from vendor/github.com/operator-framework/gini/internal/xo/cloc.go
rename to vendor/github.com/go-air/gini/internal/xo/cloc.go
index 158d40c3e..46fabfdd8 100644
--- a/vendor/github.com/operator-framework/gini/internal/xo/cloc.go
+++ b/vendor/github.com/go-air/gini/internal/xo/cloc.go
@@ -3,7 +3,7 @@
 
 package xo
 
-import "github.com/operator-framework/gini/z"
+import "github.com/go-air/gini/z"
 
 const (
 	CNull z.C = 0
diff --git a/vendor/github.com/operator-framework/gini/internal/xo/ctl.go b/vendor/github.com/go-air/gini/internal/xo/ctl.go
similarity index 99%
rename from vendor/github.com/operator-framework/gini/internal/xo/ctl.go
rename to vendor/github.com/go-air/gini/internal/xo/ctl.go
index 5a2d25ee2..dc267a2dd 100644
--- a/vendor/github.com/operator-framework/gini/internal/xo/ctl.go
+++ b/vendor/github.com/go-air/gini/internal/xo/ctl.go
@@ -7,7 +7,7 @@ import (
 	"sync"
 	"time"
 
-	"github.com/operator-framework/gini/z"
+	"github.com/go-air/gini/z"
 )
 
 // Type Ctl encapsulates low level asynchronous control
diff --git a/vendor/github.com/operator-framework/gini/internal/xo/derive.go b/vendor/github.com/go-air/gini/internal/xo/derive.go
similarity index 99%
rename from vendor/github.com/operator-framework/gini/internal/xo/derive.go
rename to vendor/github.com/go-air/gini/internal/xo/derive.go
index 988acf99f..a2e2b6267 100644
--- a/vendor/github.com/operator-framework/gini/internal/xo/derive.go
+++ b/vendor/github.com/go-air/gini/internal/xo/derive.go
@@ -6,7 +6,7 @@ package xo
 import (
 	"fmt"
 
-	"github.com/operator-framework/gini/z"
+	"github.com/go-air/gini/z"
 )
 
 type Deriver struct {
diff --git a/vendor/github.com/operator-framework/gini/internal/xo/dimacs.go b/vendor/github.com/go-air/gini/internal/xo/dimacs.go
similarity index 91%
rename from vendor/github.com/operator-framework/gini/internal/xo/dimacs.go
rename to vendor/github.com/go-air/gini/internal/xo/dimacs.go
index 9478b14a8..0ac4113dc 100644
--- a/vendor/github.com/operator-framework/gini/internal/xo/dimacs.go
+++ b/vendor/github.com/go-air/gini/internal/xo/dimacs.go
@@ -3,7 +3,7 @@
 
 package xo
 
-import "github.com/operator-framework/gini/z"
+import "github.com/go-air/gini/z"
 
 // Type DimacsVis implements dimacs.Vis for constructing
 // solvers from dimacs cnf files.
diff --git a/vendor/github.com/operator-framework/gini/internal/xo/doc.go b/vendor/github.com/go-air/gini/internal/xo/doc.go
similarity index 100%
rename from vendor/github.com/operator-framework/gini/internal/xo/doc.go
rename to vendor/github.com/go-air/gini/internal/xo/doc.go
diff --git a/vendor/github.com/operator-framework/gini/internal/xo/guess.go b/vendor/github.com/go-air/gini/internal/xo/guess.go
similarity index 99%
rename from vendor/github.com/operator-framework/gini/internal/xo/guess.go
rename to vendor/github.com/go-air/gini/internal/xo/guess.go
index f5d8414df..781646795 100644
--- a/vendor/github.com/operator-framework/gini/internal/xo/guess.go
+++ b/vendor/github.com/go-air/gini/internal/xo/guess.go
@@ -6,7 +6,7 @@ package xo
 import (
 	"math"
 
-	"github.com/operator-framework/gini/z"
+	"github.com/go-air/gini/z"
 )
 
 const (
diff --git a/vendor/github.com/operator-framework/gini/internal/xo/luby.go b/vendor/github.com/go-air/gini/internal/xo/luby.go
similarity index 100%
rename from vendor/github.com/operator-framework/gini/internal/xo/luby.go
rename to vendor/github.com/go-air/gini/internal/xo/luby.go
diff --git a/vendor/github.com/operator-framework/gini/internal/xo/phases.go b/vendor/github.com/go-air/gini/internal/xo/phases.go
similarity index 93%
rename from vendor/github.com/operator-framework/gini/internal/xo/phases.go
rename to vendor/github.com/go-air/gini/internal/xo/phases.go
index 1f3bda755..a04be18aa 100644
--- a/vendor/github.com/operator-framework/gini/internal/xo/phases.go
+++ b/vendor/github.com/go-air/gini/internal/xo/phases.go
@@ -1,6 +1,6 @@
 package xo
 
-import "github.com/operator-framework/gini/z"
+import "github.com/go-air/gini/z"
 
 type phases z.Var
 
diff --git a/vendor/github.com/operator-framework/gini/internal/xo/s.go b/vendor/github.com/go-air/gini/internal/xo/s.go
similarity index 99%
rename from vendor/github.com/operator-framework/gini/internal/xo/s.go
rename to vendor/github.com/go-air/gini/internal/xo/s.go
index 7d1063962..9f7d86569 100644
--- a/vendor/github.com/operator-framework/gini/internal/xo/s.go
+++ b/vendor/github.com/go-air/gini/internal/xo/s.go
@@ -11,9 +11,9 @@ import (
 	"sync"
 	"time"
 
-	"github.com/operator-framework/gini/dimacs"
-	"github.com/operator-framework/gini/inter"
-	"github.com/operator-framework/gini/z"
+	"github.com/go-air/gini/dimacs"
+	"github.com/go-air/gini/inter"
+	"github.com/go-air/gini/z"
 )
 
 const (
diff --git a/vendor/github.com/operator-framework/gini/internal/xo/stats.go b/vendor/github.com/go-air/gini/internal/xo/stats.go
similarity index 100%
rename from vendor/github.com/operator-framework/gini/internal/xo/stats.go
rename to vendor/github.com/go-air/gini/internal/xo/stats.go
diff --git a/vendor/github.com/operator-framework/gini/internal/xo/tracer.go b/vendor/github.com/go-air/gini/internal/xo/tracer.go
similarity index 100%
rename from vendor/github.com/operator-framework/gini/internal/xo/tracer.go
rename to vendor/github.com/go-air/gini/internal/xo/tracer.go
diff --git a/vendor/github.com/operator-framework/gini/internal/xo/trail.go b/vendor/github.com/go-air/gini/internal/xo/trail.go
similarity index 99%
rename from vendor/github.com/operator-framework/gini/internal/xo/trail.go
rename to vendor/github.com/go-air/gini/internal/xo/trail.go
index 9f67388c8..b7aa04d64 100644
--- a/vendor/github.com/operator-framework/gini/internal/xo/trail.go
+++ b/vendor/github.com/go-air/gini/internal/xo/trail.go
@@ -7,7 +7,7 @@ import (
 	"bytes"
 	"fmt"
 
-	"github.com/operator-framework/gini/z"
+	"github.com/go-air/gini/z"
 )
 
 type late struct {
diff --git a/vendor/github.com/operator-framework/gini/internal/xo/vars.go b/vendor/github.com/go-air/gini/internal/xo/vars.go
similarity index 98%
rename from vendor/github.com/operator-framework/gini/internal/xo/vars.go
rename to vendor/github.com/go-air/gini/internal/xo/vars.go
index 02a25fd98..431dccf2e 100644
--- a/vendor/github.com/operator-framework/gini/internal/xo/vars.go
+++ b/vendor/github.com/go-air/gini/internal/xo/vars.go
@@ -7,7 +7,7 @@ import (
 	"fmt"
 	"strings"
 
-	"github.com/operator-framework/gini/z"
+	"github.com/go-air/gini/z"
 )
 
 type Vars struct {
diff --git a/vendor/github.com/operator-framework/gini/internal/xo/watch.go b/vendor/github.com/go-air/gini/internal/xo/watch.go
similarity index 97%
rename from vendor/github.com/operator-framework/gini/internal/xo/watch.go
rename to vendor/github.com/go-air/gini/internal/xo/watch.go
index 4e3063574..fdd753f79 100644
--- a/vendor/github.com/operator-framework/gini/internal/xo/watch.go
+++ b/vendor/github.com/go-air/gini/internal/xo/watch.go
@@ -6,7 +6,7 @@ package xo
 import (
 	"fmt"
 
-	"github.com/operator-framework/gini/z"
+	"github.com/go-air/gini/z"
 )
 
 // Watch holds other blocking literal, clause location (
diff --git a/vendor/github.com/operator-framework/gini/logic/c.go b/vendor/github.com/go-air/gini/logic/c.go
similarity index 98%
rename from vendor/github.com/operator-framework/gini/logic/c.go
rename to vendor/github.com/go-air/gini/logic/c.go
index 081d32cc6..c42c9e582 100644
--- a/vendor/github.com/operator-framework/gini/logic/c.go
+++ b/vendor/github.com/go-air/gini/logic/c.go
@@ -4,8 +4,8 @@
 package logic
 
 import (
-	"github.com/operator-framework/gini/inter"
-	"github.com/operator-framework/gini/z"
+	"github.com/go-air/gini/inter"
+	"github.com/go-air/gini/z"
 )
 
 // C represents a formula or combinational circuit.
@@ -376,5 +376,5 @@ func (p *C) grow() {
 }
 
 func strashCode(a, b z.Lit) uint32 {
-	return uint32((a << 13) * b)
+	return uint32(^(a << 13) * b)
 }
diff --git a/vendor/github.com/operator-framework/gini/logic/card.go b/vendor/github.com/go-air/gini/logic/card.go
similarity index 98%
rename from vendor/github.com/operator-framework/gini/logic/card.go
rename to vendor/github.com/go-air/gini/logic/card.go
index af63f2ff2..2f8a074ce 100644
--- a/vendor/github.com/operator-framework/gini/logic/card.go
+++ b/vendor/github.com/go-air/gini/logic/card.go
@@ -4,7 +4,7 @@
 package logic
 
 import (
-	"github.com/operator-framework/gini/z"
+	"github.com/go-air/gini/z"
 )
 
 // Card provides an interface for different implementations
diff --git a/vendor/github.com/operator-framework/gini/logic/doc.go b/vendor/github.com/go-air/gini/logic/doc.go
similarity index 100%
rename from vendor/github.com/operator-framework/gini/logic/doc.go
rename to vendor/github.com/go-air/gini/logic/doc.go
diff --git a/vendor/github.com/operator-framework/gini/logic/roll.go b/vendor/github.com/go-air/gini/logic/roll.go
similarity index 97%
rename from vendor/github.com/operator-framework/gini/logic/roll.go
rename to vendor/github.com/go-air/gini/logic/roll.go
index abd501997..3ec3c8af6 100644
--- a/vendor/github.com/operator-framework/gini/logic/roll.go
+++ b/vendor/github.com/go-air/gini/logic/roll.go
@@ -3,7 +3,7 @@
 
 package logic
 
-import "github.com/operator-framework/gini/z"
+import "github.com/go-air/gini/z"
 
 // Roll creates an unroller of sequential logic into
 // combinational logic.
diff --git a/vendor/github.com/operator-framework/gini/logic/s.go b/vendor/github.com/go-air/gini/logic/s.go
similarity index 98%
rename from vendor/github.com/operator-framework/gini/logic/s.go
rename to vendor/github.com/go-air/gini/logic/s.go
index badcae284..2669aba57 100644
--- a/vendor/github.com/operator-framework/gini/logic/s.go
+++ b/vendor/github.com/go-air/gini/logic/s.go
@@ -3,7 +3,7 @@
 
 package logic
 
-import "github.com/operator-framework/gini/z"
+import "github.com/go-air/gini/z"
 
 // S adds sequential elements to C, gini's combinational
 // logic representation.
diff --git a/vendor/github.com/operator-framework/gini/s.go b/vendor/github.com/go-air/gini/s.go
similarity index 84%
rename from vendor/github.com/operator-framework/gini/s.go
rename to vendor/github.com/go-air/gini/s.go
index caa6281b6..bdea6ae82 100644
--- a/vendor/github.com/operator-framework/gini/s.go
+++ b/vendor/github.com/go-air/gini/s.go
@@ -3,7 +3,7 @@
 
 package gini
 
-import "github.com/operator-framework/gini/inter"
+import "github.com/go-air/gini/inter"
 
 // NewS creates a new solver, which is the Gini
 // implementation of inter.S.
diff --git a/vendor/github.com/operator-framework/gini/sv.go b/vendor/github.com/go-air/gini/sv.go
similarity index 95%
rename from vendor/github.com/operator-framework/gini/sv.go
rename to vendor/github.com/go-air/gini/sv.go
index 546b93c0c..f6a525fa3 100644
--- a/vendor/github.com/operator-framework/gini/sv.go
+++ b/vendor/github.com/go-air/gini/sv.go
@@ -6,8 +6,8 @@ package gini
 import (
 	"time"
 
-	"github.com/operator-framework/gini/inter"
-	"github.com/operator-framework/gini/z"
+	"github.com/go-air/gini/inter"
+	"github.com/go-air/gini/z"
 )
 
 type svWrap struct {
diff --git a/vendor/github.com/operator-framework/gini/z/c.go b/vendor/github.com/go-air/gini/z/c.go
similarity index 100%
rename from vendor/github.com/operator-framework/gini/z/c.go
rename to vendor/github.com/go-air/gini/z/c.go
diff --git a/vendor/github.com/operator-framework/gini/z/doc.go b/vendor/github.com/go-air/gini/z/doc.go
similarity index 100%
rename from vendor/github.com/operator-framework/gini/z/doc.go
rename to vendor/github.com/go-air/gini/z/doc.go
diff --git a/vendor/github.com/operator-framework/gini/z/dv.go b/vendor/github.com/go-air/gini/z/dv.go
similarity index 100%
rename from vendor/github.com/operator-framework/gini/z/dv.go
rename to vendor/github.com/go-air/gini/z/dv.go
diff --git a/vendor/github.com/operator-framework/gini/z/lit.go b/vendor/github.com/go-air/gini/z/lit.go
similarity index 100%
rename from vendor/github.com/operator-framework/gini/z/lit.go
rename to vendor/github.com/go-air/gini/z/lit.go
diff --git a/vendor/github.com/operator-framework/gini/z/var.go b/vendor/github.com/go-air/gini/z/var.go
similarity index 100%
rename from vendor/github.com/operator-framework/gini/z/var.go
rename to vendor/github.com/go-air/gini/z/var.go
diff --git a/vendor/github.com/operator-framework/gini/README.md b/vendor/github.com/operator-framework/gini/README.md
deleted file mode 100644
index 42d6ff2fb..000000000
--- a/vendor/github.com/operator-framework/gini/README.md
+++ /dev/null
@@ -1,339 +0,0 @@
-# Gini SAT Solver
-
-The Gini sat solver is a fast, clean SAT solver written in Go. It is to our knowledge
-the first ever performant pure-Go SAT solver made available.
-
-| [![Build Status](https://travis-ci.org/irifrance/gini.svg?branch=master)](https://travis-ci.org/irifrance/gini) | [![GoDoc](https://godoc.org/github.com/irifrance/gini?status.svg)](https://godoc.org/github.com/irifrance/gini) | [Google Group](https://groups.google.com/d/forum/ginisat) |
-------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------|
-
-This solver is fully open source, originally developped at IRI France.
-
-
-## Build/Install
-
-For the impatient:
-
-    go get github.com/irifrance/gini...
-
-I recommend however building the package github.com/irifrance/gini/internal/xo with bounds checking
-turned off.  This package is all about anything-goes performance and is the workhorse behind most of
-the gini sat solver.  It is also extensively tested and well benchmarked, so it should not pose any
-safety threat to client code.  This makes a signficant speed difference (maybe 10%) on long running
-problems.
-
-
-## The SAT Problem
-
-The SAT problem is perhaps the most famous NP-complete problem.  As such, SAT
-solvers can be used to try to solve hard problems, such as travelling salesman
-or RSA cracking. In practice, many SAT problems are quite easy (but not
-decryption problems...yet).  The solvers are used in software verification,
-hardware verification and testing, AI planning, routing, etc.
-
-The SAT problem is a Boolean problem.  All variables can either be true or
-false, but nothing else.  The SAT problem solves systems of Boolean
-constraints, called clauses.  Namely, SAT solvers work on conjunctive normal
-form problems (CNFs).  There are many ways to efficiently code arbitrary logic
-into CNF, so this is not so much a restricting factor.  Nonetheless, we present
-CNF and the problem below in a brief self-contained fashion which we find
-useful.  Readers interested in more depth should consult Wikipedia, or The
-Handbook of Satisfiability, or Donald Knuth's latest volume of The Art of
-Computer Programming.
-
-### CNF
-
-A CNF is a conjunction of clauses
-
-    c1 and c2 and ... and cM
-
-Each c[i], i in [1..M], is a clause, which is of the form
-
-    m1 or m2 or ... or mN
-
-where each m[i], i in [1..N] is either a Boolean variable (such as x), or the
-negation of a Boolean variable (such as not(y)).  An expression which is either
-a Boolean variable or its negation is called a "literal".
-
-In the following, we refer to variables simply by integers 1,2,3,...
-
-Clauses are often written in succint form
-
-    -3 11 12 14 -257
-
-Numerical negation indicates logical negation, and spaces are disjunctions
-"or".  Sometimes "+" is used for "or".
-
-Conjunctions are just concatenation of clauses.  We can parenthesize clauses
-such as
-
-    (1 -2) (2 -3) (3 -4) (4 -1)
-
-which expresses a set of clauses whose satisfying assignments are
-
-    {1,2,3,4}
-        or
-    {-1,-2,-3,-4}
-
-### Models
-
-A model of a CNF is a value for each of the variables which makes every clause 
-in the CNF true.  The SAT problem is determining whether or not a model exists
-for a given set of clauses.
-
-
-### Proofs
-
-#### Resolution
-
-Resolution is a form of logical reasoning with conjunctions of clauses.  Given
-2 clauses of the form
-
-    C + v
-and
-
-    D + -v
-
-We can conclude that 
-
-    C + D
-
-must be true.
-
-Here, C and D are arbitrary clauses.
-
-Resolution proof of unsatisfiability is a derivation of the empty disjuction
-(false) by means of resolution.  Resolution proofs, even minimally sized ones,
-can be very large, exponentially larger than the input problem.
-
-Modern SAT solvers mostly rely on performing operations which correspond to
-bounded size (in terms of number of variables) number of resolutions.  Given
-this fact together with the fact that the minimal proofs can be exponentially
-large in the number of variables, some problems can take an exponential amount
-of time.
-
-Nonetheless, many SAT solvers have heuristics and are optimised so much that
-even hard problems become tractable.  With up to several tens of millions of
-resolutions happening per second on one modern single core CPU, even problems
-with known exponential bounds on resolution steps can be solved.
-
-## Solving Formulas and Circuits
-
-Gini provides a simple and efficient logic modelling library which supports
-easy construction of arbitrary Boolean formulas.  The library uses and-inverter
-graphs, structural hashing, constant propagation and can be used for
-constructing compact formulas with a rich set of Boolean operators.  The
-circuit type implements an interface which makes it plug into a solver
-automatically.  In fact, the circuit type uses the same representation for
-literals as the solver, so there is no need to map between solver and circuit
-variables.
-
-Additionally, sequential circuits are supported.  The sequential part of the
-logic library provides memory elements (latches) which are evaluated initially
-as inputs and take a "next" value which provides input to the next cycle of the
-circuit.  The library supports unrolling sequential circuits for a fixed number
-of cycles to arrive at a non-sequential formula which can then be checked for
-satisfiability using solving tools.
-
-Gini also supports cardinality constraints which can constrain how many of a 
-set of Boolean variables are true.  Cardinality constraints in turn provide
-an easy means of doing optimisation.  Gini uses sorting networks to code
-cardinality constraints into clauses.  Sorting networks are a good general
-purpose means of handling cardinality constraints in a problem context which
-also contains lots of purely Boolean logic (implicitly or not).
-
-Most SAT use cases use a front end for modelling arbitrary formulas.  When formats
-are needed for interchange, Gini supports the following.
-
-### Aiger
-
-Gini supports [aiger version 1.9](http://fmv.jku.at/aiger/) in conjunction
-with its logic library.  The logic.C and logic.S circuit types can be 
-stored, exchanged, read and written in aiger ascii and binary formats.
-
-### Dimacs
-
-CNF Dimacs files, which are an ancient widely used format for representing CNF
-formulas.  Dimacs files are usually used for benchmarking solvers, to eliminate
-the formula representation layer.  The fact that the format is more or less
-universally supported amongst SAT solvers leads some SAT users to use this
-format, even though there is I/O, CNF translation,  and parsing overhead by
-comparison to using a logic library.
-
-
-## Optimisation
-
-With Cardinality constraints, optimisation is easy
-
-    import "github.com/irifrance/gini"
-    import "github.com/irifrance/gini/logic"
-
-    c := logic.NewC()
-
-
-    // suppose we encode package constraints for a module in the circuit c
-    // and we have a slice S of dependent packages P each of which has an attribute
-    // P.needsRepl which indicates whether or not it needs to be replaced (of type
-    // github.com/irifrance/gini/z.Lit)
-
-    repls := make([]z.Lit, 0, 1<<23)
-    for _, p := range pkgs {
-        repls = append(repls, p.needsRepl)
-    }
-
-    // make a cardinality constraints object
-    cards := c.CardSort(repls)
-
-    // loop through the constraints (note a linear search
-    // can be faster than a binary search in this case because the underlying solver
-    // often has locality of logic cache w.r.t. cardinality constraints)
-    s := gini.New()
-    c.ToCnf(s)
-    minRepls := -1
-    for i := range repls {
-        s.Assume(cards.Leq(i))
-        if s.Solve() == 1 {
-            minRepls = i
-            break
-        }
-    }
-
-    // use the model, if one was found, from s to propose a build
-
-## Activation Literals
-
-Gini supports recycling activation literals with the 
-[Activatable interface](http://godoc.org/github.com/irifrance/gini/inter#Activatable)
-
-Even without recycling, activation literals provide an easy way to solve MAXSAT problems:
-just activate each clause, use a cardinality constraint on the activation literals,
-and then optimize the output.
-
-With recycling, one can do much more, such as activating and deactivating sets of clauses,
-and constructing the clauses on the fly.  Activations work underneath test scopes and
-assumptions, making the interface for Gini perhaps the most flexible available.
-
-
-## Performance
-
-In applications, SAT problems normally have an exponential tail runtime
-distribution with a strong bias towards bigger problems populating the longer
-runtime part of the distribution.  So in practice, a good rule of thumb is 1 in
-N problems will on average take longer than time alotted to solve it for a
-problem of a given size, and then one measures N experimentally.  Very often,
-despite the NP nature of SAT, an application can be designed to use a SAT solver
-in a way that problems almost never take too long.  Additionally, the hardest known
-hand-crafted problems for CDCL solvers which take significant time involve at least 
-a few hundred variables.  So if you're application has only a few hundred variables,
-you're probably not going to have any performance problems at all with any solver.
-
-As in almost every solver, the core CDCL solver in Gini is the workhorse and is a 
-good general purpose solver.  Some specific applications do benefit from 
-pre- or in-processing, and some some applications may not be useable with such 
-techniques.  Other solvers provide more and better pre- or in-processing than Gini
-and help is welcome in adding such solving techniques to Gini.
-
-The core CDCL solver in Gini has been compared with that in MiniSAT and PicoSAT,
-two standard such solvers on randomly chosen SAT competition problems.  In this
-evaluation, Gini out performed PicoSAT and was neck-in-neck with MiniSAT.  The
-core CDCL solver in Gini also measures up to PicoSAT and MiniSAT in terms of
-"propagations per second", indicating the core routines are themselves competitive
-with these solvers, not only the heuristics.  This level of performance has not to 
-our knowledge been achieved by other sat solvers in Go, such as go-sat or gophersat.
-
-While the above evaluation casts a fairly wide net over application domains and
-problem difficulty, the performance of sat solvers and underlying algorithms are 
-fundamentally hard to predict in any rigorous way.  So your experience may differ,
-but we are confident Gini's core solver is a well positioned alternative to standard
-high-performance CDCL solvers in C/C++.  We encourage you to give it a try and welcome
-any comparisons.
-
-### Benchmarking
-
-To that end, gini comes with a nifty SAT solver benchmarking tool which allows
-to easily select benchmarks into a "bench" format, which is just a particular
-structure of directories and files.  The tool can then also run solvers 
-on such generated "bench"'s, enforcing various timeouts and logging all details,
-again in a standard format.  To tool then can compare the results in various 
-fashions, printing out scatter and cactus plots (in UTF8/ascii art) of various 
-user selectable subsets of the benchmark run.
-
-You may find this tool useful to fork and adopt to benchmark a new kind of
-program.  The benchmarking mechanism is appropriate for any "solver" like
-software (SMT, CPLEX, etc) where runtimes vary and are unpredictable and
-potentially high.  If you do so, please follow the license or ask for
-alternatives.
-
-## Concurrency
-
-Gini is written in Go and uses several goroutines by default for garbage
-collection and system call scheduling.  There is a "core" single-goroutine
-solver, xo, which is in an internal package for gutsy low level SAT hackers. 
-
-### Connections to solving processes
-
-Gini provides safe connections to solving processes which are guaranteed to not
-lose any solution found, can pause and resume, run with a timeout, test without
-solving, etc.
-
-### Solve-time copyable solvers.
-
-Gini provides copyable solvers, which can be safely copied *at solvetime during
-a pause*.
-
-### Ax
-Gini provides an "Assumption eXchange" package for deploying solves
-under different sets of assumptions to the same set of underlying constraints
-in parallel. This can give linear speed up in tasks, such as PDR/IC3, which 
-generate lots of assumptions.
-
-We hope to extend this with clause sharing soon, which would give 
-superlinear speedup according to the literature.
-
-## Distributed and CRISP
-
-Gini provides a definition and reference implementation for
-[CRISP-1.0](https://github.com/irifrance/gini/blob/master/doc/crisp/crisp.pdf),
-the compressed incremental SAT protocol.  The protocol is a client-server wire
-protocol which can dispatch an incremental sat solver with very little overhead
-as compared to direct API calls.  The advantage of using a protocol is that it
-allows arbitrary tools to implement the solving on arbitrary hardware without
-affecting the client.  
-
-Many SAT applications are incremental and easily solve huge numbers of problems
-while only a few problems are hard.  CRISP facilitates pulling out the big guns
-for the hard problems while not affecting performance for easy problems.  Big
-guns tend to be harder to integrate into applications because of compilation
-issues, hardware requirements, size and complexity of the code base, etc.
-Applications that use CRISP can truly isolate themselves from the woes of
-integrating big guns while benefiting on hard problems.
-
-CRISP also allows language independent incremental SAT solving.  The
-applications and solvers can be readily implemented without the headache of
-synchronizing programming language, compilers, or coding style.
-
-We are planning on implementing some CRISP extensions, namely the multiplexing
-interface which will enable (possibly remote) clients to control
-programmatically partitioning or queuing of related SAT problems.
-
-The CRISP protocol provides a basis for distributed solving.  Gini implements
-a CRISP-1.0 client and server.  
-
-A command, crispd, is supplied for the CRISP server.
-
-## Citing Gini
-
-Zenodo DOI based citations and download:
-[![DOI](https://zenodo.org/badge/64034957.svg)](https://zenodo.org/badge/latestdoi/64034957)
-
-BibText:
-```
-@misc{scott_cotton_2019_2553490,
-  author       = {Scott  Cotton},
-  title        = {irifrance/gini: Sapeur},
-  month        = jan,
-  year         = 2019,
-  doi          = {10.5281/zenodo.2553490},
-  url          = {https://doi.org/10.5281/zenodo.2553490}
-}
-```
-
diff --git a/vendor/github.com/operator-framework/gini/go.mod b/vendor/github.com/operator-framework/gini/go.mod
deleted file mode 100644
index 15f34b3a9..000000000
--- a/vendor/github.com/operator-framework/gini/go.mod
+++ /dev/null
@@ -1,1 +0,0 @@
-module github.com/operator-framework/gini
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 74d4d25cf..7230e8564 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -211,6 +211,14 @@ github.com/fsnotify/fsnotify
 # github.com/ghodss/yaml v1.0.0
 ## explicit
 github.com/ghodss/yaml
+# github.com/go-air/gini v1.0.4
+## explicit
+github.com/go-air/gini
+github.com/go-air/gini/dimacs
+github.com/go-air/gini/inter
+github.com/go-air/gini/internal/xo
+github.com/go-air/gini/logic
+github.com/go-air/gini/z
 # github.com/go-bindata/go-bindata/v3 v3.1.3
 ## explicit
 github.com/go-bindata/go-bindata/v3
@@ -507,14 +515,6 @@ github.com/operator-framework/api/pkg/validation
 github.com/operator-framework/api/pkg/validation/errors
 github.com/operator-framework/api/pkg/validation/interfaces
 github.com/operator-framework/api/pkg/validation/internal
-# github.com/operator-framework/gini v1.1.0
-## explicit
-github.com/operator-framework/gini
-github.com/operator-framework/gini/dimacs
-github.com/operator-framework/gini/inter
-github.com/operator-framework/gini/internal/xo
-github.com/operator-framework/gini/logic
-github.com/operator-framework/gini/z
 # github.com/operator-framework/operator-registry v1.17.5
 ## explicit
 github.com/operator-framework/operator-registry/internal/model
@@ -1861,4 +1861,3 @@ sigs.k8s.io/structured-merge-diff/v4/value
 sigs.k8s.io/yaml
 # github.com/openshift/api => github.com/openshift/api v0.0.0-20200331152225-585af27e34fd
 # github.com/openshift/client-go => github.com/openshift/client-go v0.0.0-20200326155132-2a6cd50aedd0
-# sigs.k8s.io/structured-merge-diff => sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06


Test patch:
diff --git a/pkg/controller/operators/catalog/subscriptions_test.go b/pkg/controller/operators/catalog/subscriptions_test.go
index 15e44269f..4d35a8039 100644
--- a/pkg/controller/operators/catalog/subscriptions_test.go
+++ b/pkg/controller/operators/catalog/subscriptions_test.go
@@ -155,6 +155,14 @@ func TestSyncSubscriptions(t *testing.T) {
 						},
 						LastUpdated:           now,
 						InstallPlanGeneration: 1,
+						Conditions: []v1alpha1.SubscriptionCondition{
+							{
+								Type:    "ResolutionFailed",
+								Status:  corev1.ConditionFalse,
+								Reason:  "",
+								Message: "",
+							},
+						},
 					},
 				},
 			},
@@ -298,6 +306,14 @@ func TestSyncSubscriptions(t *testing.T) {
 						},
 						LastUpdated:           now,
 						InstallPlanGeneration: 1,
+						Conditions: []v1alpha1.SubscriptionCondition{
+							{
+								Type:    "ResolutionFailed",
+								Status:  corev1.ConditionFalse,
+								Reason:  "",
+								Message: "",
+							},
+						},
 					},
 				},
 			},
@@ -446,6 +462,14 @@ func TestSyncSubscriptions(t *testing.T) {
 						},
 						InstallPlanGeneration: 1,
 						LastUpdated:           now,
+						Conditions: []v1alpha1.SubscriptionCondition{
+							{
+								Type:    "ResolutionFailed",
+								Status:  corev1.ConditionFalse,
+								Reason:  "",
+								Message: "",
+							},
+						},
 					},
 				},
 			},
@@ -599,6 +623,14 @@ func TestSyncSubscriptions(t *testing.T) {
 						},
 						LastUpdated:           now,
 						InstallPlanGeneration: 1,
+						Conditions: []v1alpha1.SubscriptionCondition{
+							{
+								Type:    "ResolutionFailed",
+								Status:  corev1.ConditionFalse,
+								Reason:  "",
+								Message: "",
+							},
+						},
 					},
 				},
 			},
@@ -775,6 +807,14 @@ func TestSyncSubscriptions(t *testing.T) {
 						},
 						LastUpdated:           now,
 						InstallPlanGeneration: 1,
+						Conditions: []v1alpha1.SubscriptionCondition{
+							{
+								Type:    "ResolutionFailed",
+								Status:  corev1.ConditionFalse,
+								Reason:  "",
+								Message: "",
+							},
+						},
 					},
 				},
 			},
@@ -958,6 +998,14 @@ func TestSyncSubscriptions(t *testing.T) {
 						},
 						LastUpdated:           now,
 						InstallPlanGeneration: 2,
+						Conditions: []v1alpha1.SubscriptionCondition{
+							{
+								Type:    "ResolutionFailed",
+								Status:  corev1.ConditionFalse,
+								Reason:  "",
+								Message: "",
+							},
+						},
 					},
 				},
 			},
diff --git a/pkg/controller/registry/resolver/cache/cache_test.go b/pkg/controller/registry/resolver/cache/cache_test.go
index ecb4f14b2..f09545645 100644
--- a/pkg/controller/registry/resolver/cache/cache_test.go
+++ b/pkg/controller/registry/resolver/cache/cache_test.go
@@ -4,113 +4,37 @@ import (
 	"context"
 	"errors"
 	"fmt"
-	"io"
 	"math/rand"
 	"strconv"
 	"testing"
 	"time"
 
-	"github.com/sirupsen/logrus"
-	"github.com/sirupsen/logrus/hooks/test"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 
-	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry"
-	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorlister"
 	"github.com/operator-framework/operator-registry/pkg/api"
-	"github.com/operator-framework/operator-registry/pkg/client"
-	opregistry "github.com/operator-framework/operator-registry/pkg/registry"
 )
 
-type BundleStreamStub struct {
-	Bundles []*api.Bundle
-}
-
-func (s *BundleStreamStub) Recv() (*api.Bundle, error) {
-	if len(s.Bundles) == 0 {
-		return nil, io.EOF
-	}
-	b := s.Bundles[0]
-	s.Bundles = s.Bundles[1:]
-	return b, nil
-}
-
-type RegistryClientStub struct {
-	BundleIterator *client.BundleIterator
-
-	ListBundlesError error
-}
-
-func (s *RegistryClientStub) Get() (client.Interface, error) {
-	return s, nil
-}
-
-func (s *RegistryClientStub) GetBundle(ctx context.Context, packageName, channelName, csvName string) (*api.Bundle, error) {
-	return nil, nil
-}
-
-func (s *RegistryClientStub) GetBundleInPackageChannel(ctx context.Context, packageName, channelName string) (*api.Bundle, error) {
-	return nil, nil
-}
-
-func (s *RegistryClientStub) GetReplacementBundleInPackageChannel(ctx context.Context, currentName, packageName, channelName string) (*api.Bundle, error) {
-	return nil, nil
-}
-
-func (s *RegistryClientStub) GetBundleThatProvides(ctx context.Context, group, version, kind string) (*api.Bundle, error) {
-	return nil, nil
-}
-
-func (s *RegistryClientStub) ListBundles(ctx context.Context) (*client.BundleIterator, error) {
-	return s.BundleIterator, s.ListBundlesError
-}
-
-func (s *RegistryClientStub) GetPackage(ctx context.Context, packageName string) (*api.Package, error) {
-	return &api.Package{Name: packageName}, nil
-}
-
-func (s *RegistryClientStub) HealthCheck(ctx context.Context, reconnectTimeout time.Duration) (bool, error) {
-	return false, nil
-}
-
-func (s *RegistryClientStub) Close() error {
-	return nil
-}
-
-type RegistryClientProviderStub map[registry.CatalogKey]client.Interface
-
-func (s RegistryClientProviderStub) ClientsForNamespaces(namespaces ...string) map[registry.CatalogKey]client.Interface {
-	return s
-}
-
 func TestOperatorCacheConcurrency(t *testing.T) {
 	const (
 		NWorkers = 64
 	)
-	rcp := RegistryClientProviderStub{}
-	catsrcLister := operatorlister.NewLister().OperatorsV1alpha1().CatalogSourceLister()
-	var keys []registry.CatalogKey
+
+	sp := make(StaticSourceProvider)
+	var keys []SourceKey
 	for i := 0; i < 128; i++ {
 		for j := 0; j < 8; j++ {
-			key := registry.CatalogKey{Namespace: strconv.Itoa(i), Name: strconv.Itoa(j)}
+			key := SourceKey{Namespace: strconv.Itoa(i), Name: strconv.Itoa(j)}
 			keys = append(keys, key)
-			rcp[key] = &RegistryClientStub{
-				BundleIterator: client.NewBundleIterator(&BundleStreamStub{
-					Bundles: []*api.Bundle{{
-						CsvName: fmt.Sprintf("%s/%s", key.Namespace, key.Name),
-						ProvidedApis: []*api.GroupVersionKind{{
-							Group:   "g",
-							Version: "v1",
-							Kind:    "K",
-							Plural:  "ks",
-						}},
-					}},
-				}),
+			sp[key] = &Snapshot{
+				Entries: []*Operator{
+					{Name: fmt.Sprintf("%s/%s", key.Namespace, key.Name)},
+				},
 			}
 		}
 	}
 
-	c := NewOperatorCache(rcp, logrus.New(), catsrcLister)
+	c := New(sp)
 
 	errs := make(chan error)
 	for w := 0; w < NWorkers; w++ {
@@ -143,56 +67,52 @@ func TestOperatorCacheConcurrency(t *testing.T) {
 }
 
 func TestOperatorCacheExpiration(t *testing.T) {
-	rcp := RegistryClientProviderStub{}
-	catsrcLister := operatorlister.NewLister().OperatorsV1alpha1().CatalogSourceLister()
-	key := registry.CatalogKey{Namespace: "dummynamespace", Name: "dummyname"}
-	rcp[key] = &RegistryClientStub{
-		BundleIterator: client.NewBundleIterator(&BundleStreamStub{
-			Bundles: []*api.Bundle{{
-				CsvName: "csvname",
-				ProvidedApis: []*api.GroupVersionKind{{
-					Group:   "g",
-					Version: "v1",
-					Kind:    "K",
-					Plural:  "ks",
-				}},
-			}},
-		}),
-	}
-
-	c := NewOperatorCache(rcp, logrus.New(), catsrcLister)
+	key := SourceKey{Namespace: "dummynamespace", Name: "dummyname"}
+	ssp := make(StaticSourceProvider)
+	c := New(ssp)
 	c.ttl = 0 // instantly stale
 
-	require.Len(t, c.Namespaced("dummynamespace").Catalog(key).Find(CSVNamePredicate("csvname")), 1)
+	ssp[key] = &Snapshot{
+		Entries: []*Operator{
+			{Name: "v1"},
+		},
+	}
+	require.Len(t, c.Namespaced("dummynamespace").Catalog(key).Find(CSVNamePredicate("v1")), 1)
+
+	ssp[key] = &Snapshot{
+		Entries: []*Operator{
+			{Name: "v2"},
+		},
+	}
+	require.Len(t, c.Namespaced("dummynamespace").Catalog(key).Find(CSVNamePredicate("v1")), 0)
 }
 
 func TestOperatorCacheReuse(t *testing.T) {
-	rcp := RegistryClientProviderStub{}
-	catsrcLister := operatorlister.NewLister().OperatorsV1alpha1().CatalogSourceLister()
-	key := registry.CatalogKey{Namespace: "dummynamespace", Name: "dummyname"}
-	rcp[key] = &RegistryClientStub{
-		BundleIterator: client.NewBundleIterator(&BundleStreamStub{
-			Bundles: []*api.Bundle{{
-				CsvName: "csvname",
-				ProvidedApis: []*api.GroupVersionKind{{
-					Group:   "g",
-					Version: "v1",
-					Kind:    "K",
-					Plural:  "ks",
-				}},
-			}},
-		}),
-	}
+	key := SourceKey{Namespace: "dummynamespace", Name: "dummyname"}
+	ssp := make(StaticSourceProvider)
+	c := New(ssp)
 
-	c := NewOperatorCache(rcp, logrus.New(), catsrcLister)
+	ssp[key] = &Snapshot{
+		Entries: []*Operator{
+			{Name: "v1"},
+		},
+	}
+	require.Len(t, c.Namespaced("dummynamespace").Catalog(key).Find(CSVNamePredicate("v1")), 1)
 
-	require.Len(t, c.Namespaced("dummynamespace").Catalog(key).Find(CSVNamePredicate("csvname")), 1)
+	ssp[key] = &Snapshot{
+		Entries: []*Operator{
+			{Name: "v2"},
+		},
+	}
+	require.Len(t, c.Namespaced("dummynamespace").Catalog(key).Find(CSVNamePredicate("v1")), 1)
 }
 
-func TestCatalogSnapshotExpired(t *testing.T) {
+func TestCatalogSnapshotValid(t *testing.T) {
 	type tc struct {
 		Name     string
 		Expiry   time.Time
+		Snapshot *Snapshot
+		Error    error
 		At       time.Time
 		Expected bool
 	}
@@ -201,25 +121,51 @@ func TestCatalogSnapshotExpired(t *testing.T) {
 		{
 			Name:     "after expiry",
 			Expiry:   time.Unix(0, 1),
+			Snapshot: &Snapshot{},
+			Error:    nil,
 			At:       time.Unix(0, 2),
-			Expected: true,
+			Expected: false,
 		},
 		{
 			Name:     "before expiry",
 			Expiry:   time.Unix(0, 2),
+			Snapshot: &Snapshot{},
+			Error:    nil,
+			At:       time.Unix(0, 1),
+			Expected: true,
+		},
+		{
+			Name:     "nil snapshot",
+			Expiry:   time.Unix(0, 2),
+			Snapshot: nil,
+			Error:    errors.New(""),
+			At:       time.Unix(0, 1),
+			Expected: false,
+		},
+		{
+			Name:     "non-nil error",
+			Expiry:   time.Unix(0, 2),
+			Snapshot: &Snapshot{},
+			Error:    errors.New(""),
 			At:       time.Unix(0, 1),
 			Expected: false,
 		},
 		{
 			Name:     "at expiry",
 			Expiry:   time.Unix(0, 1),
+			Snapshot: &Snapshot{},
+			Error:    nil,
 			At:       time.Unix(0, 1),
-			Expected: true,
+			Expected: false,
 		},
 	} {
 		t.Run(tt.Name, func(t *testing.T) {
-			s := CatalogSnapshot{expiry: tt.Expiry}
-			assert.Equal(t, tt.Expected, s.Expired(tt.At))
+			s := snapshotHeader{
+				expiry:   tt.Expiry,
+				snapshot: tt.Snapshot,
+				err:      tt.Error,
+			}
+			assert.Equal(t, tt.Expected, s.Valid(tt.At))
 		})
 	}
 
@@ -287,7 +233,7 @@ func TestCatalogSnapshotFind(t *testing.T) {
 		},
 	} {
 		t.Run(tt.Name, func(t *testing.T) {
-			s := CatalogSnapshot{Operators: tt.Operators}
+			s := snapshotHeader{snapshot: &Snapshot{Entries: tt.Operators}}
 			assert.Equal(t, tt.Expected, s.Find(tt.Predicate))
 		})
 	}
@@ -295,69 +241,41 @@ func TestCatalogSnapshotFind(t *testing.T) {
 }
 
 func TestStripPluralRequiredAndProvidedAPIKeys(t *testing.T) {
-	rcp := RegistryClientProviderStub{}
-	catsrcLister := operatorlister.NewLister().OperatorsV1alpha1().CatalogSourceLister()
-	key := registry.CatalogKey{Namespace: "testnamespace", Name: "testname"}
-	rcp[key] = &RegistryClientStub{
-		BundleIterator: client.NewBundleIterator(&BundleStreamStub{
-			Bundles: []*api.Bundle{{
-				CsvName: fmt.Sprintf("%s/%s", key.Namespace, key.Name),
-				ProvidedApis: []*api.GroupVersionKind{{
-					Group:   "g",
-					Version: "v1",
-					Kind:    "K",
-					Plural:  "ks",
-				}},
-				RequiredApis: []*api.GroupVersionKind{{
-					Group:   "g2",
-					Version: "v2",
-					Kind:    "K2",
-					Plural:  "ks2",
-				}},
-				Properties: APISetToProperties(map[opregistry.APIKey]struct{}{
-					{
-						Group:   "g",
-						Version: "v1",
-						Kind:    "K",
-						Plural:  "ks",
-					}: {},
-				}, nil, false),
-				Dependencies: APISetToDependencies(map[opregistry.APIKey]struct{}{
-					{
-						Group:   "g2",
-						Version: "v2",
-						Kind:    "K2",
-						Plural:  "ks2",
-					}: {},
-				}, nil),
-			}},
-		}),
-	}
-
-	c := NewOperatorCache(rcp, logrus.New(), catsrcLister)
+	key := SourceKey{Namespace: "testnamespace", Name: "testname"}
+	o, err := NewOperatorFromBundle(&api.Bundle{
+		CsvName: fmt.Sprintf("%s/%s", key.Namespace, key.Name),
+		ProvidedApis: []*api.GroupVersionKind{{
+			Group:   "g",
+			Version: "v1",
+			Kind:    "K",
+			Plural:  "ks",
+		}},
+		RequiredApis: []*api.GroupVersionKind{{
+			Group:   "g2",
+			Version: "v2",
+			Kind:    "K2",
+			Plural:  "ks2",
+		}},
+	}, "", key, "")
 
-	nc := c.Namespaced("testnamespace")
-	result, err := AtLeast(1, nc.Find(ProvidingAPIPredicate(opregistry.APIKey{Group: "g", Version: "v1", Kind: "K"})))
 	assert.NoError(t, err)
-	assert.Equal(t, 1, len(result))
-	assert.Equal(t, "K.v1.g", result[0].ProvidedAPIs.String())
-	assert.Equal(t, "K2.v2.g2", result[0].RequiredAPIs.String())
+	assert.Equal(t, "K.v1.g", o.ProvidedAPIs.String())
+	assert.Equal(t, "K2.v2.g2", o.RequiredAPIs.String())
+}
+
+type ErrorSource struct {
+	Error error
+}
+
+func (s ErrorSource) Snapshot(context.Context) (*Snapshot, error) {
+	return nil, s.Error
 }
 
 func TestNamespaceOperatorCacheError(t *testing.T) {
-	rcp := RegistryClientProviderStub{}
-	catsrcLister := operatorlister.NewLister().OperatorsV1alpha1().CatalogSourceLister()
-	key := registry.CatalogKey{Namespace: "dummynamespace", Name: "dummyname"}
-	rcp[key] = &RegistryClientStub{
-		ListBundlesError: errors.New("testing"),
-	}
+	key := SourceKey{Namespace: "dummynamespace", Name: "dummyname"}
+	c := New(StaticSourceProvider{
+		key: ErrorSource{Error: errors.New("testing")},
+	})
 
-	logger, _ := test.NewNullLogger()
-	c := NewOperatorCache(rcp, logger, catsrcLister)
 	require.EqualError(t, c.Namespaced("dummynamespace").Error(), "error using catalog dummyname (in namespace dummynamespace): testing")
-	if snapshot, ok := c.snapshots[key]; !ok {
-		t.Fatalf("cache snapshot not found")
-	} else {
-		require.Zero(t, snapshot.expiry)
-	}
 }
diff --git a/pkg/controller/registry/resolver/cache/operators_test.go b/pkg/controller/registry/resolver/cache/operators_test.go
index cf46ad80f..5337a5b25 100644
--- a/pkg/controller/registry/resolver/cache/operators_test.go
+++ b/pkg/controller/registry/resolver/cache/operators_test.go
@@ -11,7 +11,6 @@ import (
 
 	opver "github.com/operator-framework/api/pkg/lib/version"
 	"github.com/operator-framework/api/pkg/operators/v1alpha1"
-	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry"
 	"github.com/operator-framework/operator-registry/pkg/api"
 	opregistry "github.com/operator-framework/operator-registry/pkg/registry"
 )
@@ -713,7 +712,7 @@ func TestCatalogKey_String(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			k := &registry.CatalogKey{
+			k := &SourceKey{
 				Name:      tt.fields.Name,
 				Namespace: tt.fields.Namespace,
 			}
@@ -878,7 +877,7 @@ func TestOperatorSourceInfo_String(t *testing.T) {
 			i := &OperatorSourceInfo{
 				Package: tt.fields.Package,
 				Channel: tt.fields.Channel,
-				Catalog: registry.CatalogKey{Name: tt.fields.CatalogSource, Namespace: tt.fields.CatalogSourceNamespace},
+				Catalog: SourceKey{Name: tt.fields.CatalogSource, Namespace: tt.fields.CatalogSourceNamespace},
 			}
 			if got := i.String(); got != tt.want {
 				t.Errorf("OperatorSourceInfo.String() = %v, want %v", got, tt.want)
@@ -1060,7 +1059,7 @@ func TestNewOperatorFromBundle(t *testing.T) {
 
 	type args struct {
 		bundle         *api.Bundle
-		sourceKey      registry.CatalogKey
+		sourceKey      SourceKey
 		replaces       string
 		defaultChannel string
 	}
@@ -1074,7 +1073,7 @@ func TestNewOperatorFromBundle(t *testing.T) {
 			name: "BundleNoAPIs",
 			args: args{
 				bundle:    bundleNoAPIs,
-				sourceKey: registry.CatalogKey{Name: "source", Namespace: "testNamespace"},
+				sourceKey: SourceKey{Name: "source", Namespace: "testNamespace"},
 			},
 			want: &Operator{
 				Name:         "testBundle",
@@ -1085,7 +1084,7 @@ func TestNewOperatorFromBundle(t *testing.T) {
 				SourceInfo: &OperatorSourceInfo{
 					Package: "testPackage",
 					Channel: "testChannel",
-					Catalog: registry.CatalogKey{Name: "source", Namespace: "testNamespace"},
+					Catalog: SourceKey{Name: "source", Namespace: "testNamespace"},
 				},
 			},
 		},
@@ -1093,7 +1092,7 @@ func TestNewOperatorFromBundle(t *testing.T) {
 			name: "BundleWithAPIs",
 			args: args{
 				bundle:    bundleWithAPIs,
-				sourceKey: registry.CatalogKey{Name: "source", Namespace: "testNamespace"},
+				sourceKey: SourceKey{Name: "source", Namespace: "testNamespace"},
 			},
 			want: &Operator{
 				Name:    "testBundle",
@@ -1148,7 +1147,7 @@ func TestNewOperatorFromBundle(t *testing.T) {
 				SourceInfo: &OperatorSourceInfo{
 					Package: "testPackage",
 					Channel: "testChannel",
-					Catalog: registry.CatalogKey{Name: "source", Namespace: "testNamespace"},
+					Catalog: SourceKey{Name: "source", Namespace: "testNamespace"},
 				},
 			},
 		},
@@ -1156,7 +1155,7 @@ func TestNewOperatorFromBundle(t *testing.T) {
 			name: "BundleIgnoreCSV",
 			args: args{
 				bundle:    bundleWithAPIsUnextracted,
-				sourceKey: registry.CatalogKey{Name: "source", Namespace: "testNamespace"},
+				sourceKey: SourceKey{Name: "source", Namespace: "testNamespace"},
 			},
 			want: &Operator{
 				Name:         "testBundle",
@@ -1166,7 +1165,7 @@ func TestNewOperatorFromBundle(t *testing.T) {
 				SourceInfo: &OperatorSourceInfo{
 					Package: "testPackage",
 					Channel: "testChannel",
-					Catalog: registry.CatalogKey{Name: "source", Namespace: "testNamespace"},
+					Catalog: SourceKey{Name: "source", Namespace: "testNamespace"},
 				},
 			},
 		},
@@ -1174,7 +1173,7 @@ func TestNewOperatorFromBundle(t *testing.T) {
 			name: "BundleInDefaultChannel",
 			args: args{
 				bundle:         bundleNoAPIs,
-				sourceKey:      registry.CatalogKey{Name: "source", Namespace: "testNamespace"},
+				sourceKey:      SourceKey{Name: "source", Namespace: "testNamespace"},
 				defaultChannel: "testChannel",
 			},
 			want: &Operator{
@@ -1186,7 +1185,7 @@ func TestNewOperatorFromBundle(t *testing.T) {
 				SourceInfo: &OperatorSourceInfo{
 					Package:        "testPackage",
 					Channel:        "testChannel",
-					Catalog:        registry.CatalogKey{Name: "source", Namespace: "testNamespace"},
+					Catalog:        SourceKey{Name: "source", Namespace: "testNamespace"},
 					DefaultChannel: true,
 				},
 			},
@@ -1195,7 +1194,7 @@ func TestNewOperatorFromBundle(t *testing.T) {
 			name: "BundleWithPropertiesAndDependencies",
 			args: args{
 				bundle:    bundleWithPropsAndDeps,
-				sourceKey: registry.CatalogKey{Name: "source", Namespace: "testNamespace"},
+				sourceKey: SourceKey{Name: "source", Namespace: "testNamespace"},
 			},
 			want: &Operator{
 				Name:         "testBundle",
@@ -1224,7 +1223,7 @@ func TestNewOperatorFromBundle(t *testing.T) {
 				SourceInfo: &OperatorSourceInfo{
 					Package: "testPackage",
 					Channel: "testChannel",
-					Catalog: registry.CatalogKey{Name: "source", Namespace: "testNamespace"},
+					Catalog: SourceKey{Name: "source", Namespace: "testNamespace"},
 				},
 			},
 		},
diff --git a/pkg/controller/registry/resolver/instrumented_resolver_test.go b/pkg/controller/registry/resolver/instrumented_resolver_test.go
index 4b4c57fcf..3b0aa0d59 100644
--- a/pkg/controller/registry/resolver/instrumented_resolver_test.go
+++ b/pkg/controller/registry/resolver/instrumented_resolver_test.go
@@ -5,9 +5,8 @@ import (
 	"testing"
 	"time"
 
-	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry"
-
 	"github.com/operator-framework/api/pkg/operators/v1alpha1"
+	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache"
 	"github.com/stretchr/testify/require"
 )
 
@@ -24,14 +23,14 @@ func (r *fakeResolverWithError) ResolveSteps(namespace string) ([]*v1alpha1.Step
 	return nil, nil, nil, errors.New("Fake error")
 }
 
-func (r *fakeResolverWithError) Expire(key registry.CatalogKey) {
+func (r *fakeResolverWithError) Expire(key cache.SourceKey) {
 }
 
 func (r *fakeResolverWithoutError) ResolveSteps(namespace string) ([]*v1alpha1.Step, []v1alpha1.BundleLookup, []*v1alpha1.Subscription, error) {
 	return nil, nil, nil, nil
 }
 
-func (r *fakeResolverWithoutError) Expire(key registry.CatalogKey) {
+func (r *fakeResolverWithoutError) Expire(key cache.SourceKey) {
 }
 
 func newFakeResolverWithError() *fakeResolverWithError {
diff --git a/pkg/controller/registry/resolver/resolver_test.go b/pkg/controller/registry/resolver/resolver_test.go
index a686cbdcf..1d80b0cd1 100644
--- a/pkg/controller/registry/resolver/resolver_test.go
+++ b/pkg/controller/registry/resolver/resolver_test.go
@@ -12,10 +12,11 @@ import (
 	"github.com/stretchr/testify/require"
 
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/labels"
 
 	"github.com/operator-framework/api/pkg/lib/version"
 	"github.com/operator-framework/api/pkg/operators/v1alpha1"
-	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry"
+	listersv1alpha1 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/listers/operators/v1alpha1"
 	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache"
 	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/solver"
 	"github.com/operator-framework/operator-registry/pkg/api"
@@ -27,7 +28,7 @@ func TestSolveOperators(t *testing.T) {
 	Provides := APISet
 
 	const namespace = "test-namespace"
-	catalog := registry.CatalogKey{Name: "test-catalog", Namespace: namespace}
+	catalog := cache.SourceKey{Name: "test-catalog", Namespace: namespace}
 
 	csv := existingOperator(namespace, "packageA.v1", "packageA", "alpha", "", Provides, nil, nil, nil)
 	csvs := []*v1alpha1.ClusterServiceVersion{csv}
@@ -35,20 +36,16 @@ func TestSolveOperators(t *testing.T) {
 	newSub := newSub(namespace, "packageB", "alpha", catalog)
 	subs := []*v1alpha1.Subscription{sub, newSub}
 
-	fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{
-		Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{
-			catalog: {
-				Key: catalog,
-				Operators: []*cache.Operator{
+	satResolver := SatResolver{
+		cache: cache.New(cache.StaticSourceProvider{
+			catalog: &cache.Snapshot{
+				Entries: []*cache.Operator{
 					genOperator("packageA.v1", "0.0.1", "", "packageA", "alpha", catalog.Name, catalog.Namespace, nil, nil, nil, "", false),
 					genOperator("packageB.v1", "1.0.1", "", "packageB", "alpha", catalog.Name, catalog.Namespace, nil, nil, nil, "", false),
 				},
 			},
-		},
-	}
-	satResolver := SatResolver{
-		cache: getFakeOperatorCache(fakeNamespacedOperatorCache),
-		log:   logrus.New(),
+		}),
+		log: logrus.New(),
 	}
 
 	operators, err := satResolver.SolveOperators([]string{namespace}, csvs, subs)
@@ -62,27 +59,23 @@ func TestSolveOperators(t *testing.T) {
 
 func TestDisjointChannelGraph(t *testing.T) {
 	const namespace = "test-namespace"
-	catalog := registry.CatalogKey{Name: "test-catalog", Namespace: namespace}
+	catalog := cache.SourceKey{Name: "test-catalog", Namespace: namespace}
 
 	newSub := newSub(namespace, "packageA", "alpha", catalog)
 	subs := []*v1alpha1.Subscription{newSub}
 
-	fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{
-		Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{
-			catalog: {
-				Key: catalog,
-				Operators: []*cache.Operator{
+	satResolver := SatResolver{
+		cache: cache.New(cache.StaticSourceProvider{
+			catalog: &cache.Snapshot{
+				Entries: []*cache.Operator{
 					genOperator("packageA.side1.v1", "0.0.1", "", "packageA", "alpha", catalog.Name, catalog.Namespace, nil, nil, nil, "", false),
 					genOperator("packageA.side1.v2", "0.0.2", "packageA.side1.v1", "packageA", "alpha", catalog.Name, catalog.Namespace, nil, nil, nil, "", false),
 					genOperator("packageA.side2.v1", "1.0.0", "", "packageA", "alpha", catalog.Name, catalog.Namespace, nil, nil, nil, "", false),
 					genOperator("packageA.side2.v2", "2.0.0", "packageA.side2.v1", "packageA", "alpha", catalog.Name, catalog.Namespace, nil, nil, nil, "", false),
 				},
 			},
-		},
-	}
-	satResolver := SatResolver{
-		cache: getFakeOperatorCache(fakeNamespacedOperatorCache),
-		log:   logrus.New(),
+		}),
+		log: logrus.New(),
 	}
 
 	_, err := satResolver.SolveOperators([]string{namespace}, nil, subs)
@@ -93,7 +86,7 @@ func TestPropertiesAnnotationHonored(t *testing.T) {
 	const (
 		namespace = "olm"
 	)
-	community := registry.CatalogKey{"community", namespace}
+	community := cache.SourceKey{"community", namespace}
 
 	csv := existingOperator(namespace, "packageA.v1", "packageA", "alpha", "", nil, nil, nil, nil)
 	csv.Annotations = map[string]string{"operatorframework.io/properties": `{"properties":[{"type":"olm.package","value":{"packageName":"packageA","version":"1.0.0"}}]}`}
@@ -104,17 +97,13 @@ func TestPropertiesAnnotationHonored(t *testing.T) {
 
 	b := genOperator("packageB.v1", "1.0.1", "", "packageB", "alpha", "community", "olm", nil, nil, []*api.Dependency{{Type: "olm.package", Value: `{"packageName":"packageA","version":"1.0.0"}`}}, "", false)
 
-	fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{
-		Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{
-			community: {
-				Key:       community,
-				Operators: []*cache.Operator{b},
-			},
-		},
-	}
 	satResolver := SatResolver{
-		cache: getFakeOperatorCache(fakeNamespacedOperatorCache),
-		log:   logrus.New(),
+		cache: cache.New(cache.StaticSourceProvider{
+			community: &cache.Snapshot{
+				Entries: []*cache.Operator{b},
+			},
+		}),
+		log: logrus.New(),
 	}
 
 	operators, err := satResolver.SolveOperators([]string{"olm"}, csvs, subs)
@@ -131,7 +120,7 @@ func TestSolveOperators_MultipleChannels(t *testing.T) {
 	Provides := APISet
 
 	namespace := "olm"
-	catalog := registry.CatalogKey{"community", namespace}
+	catalog := cache.SourceKey{"community", namespace}
 
 	csv := existingOperator(namespace, "packageA.v1", "packageA", "alpha", "", Provides, nil, nil, nil)
 	csvs := []*v1alpha1.ClusterServiceVersion{csv}
@@ -139,21 +128,17 @@ func TestSolveOperators_MultipleChannels(t *testing.T) {
 	newSub := newSub(namespace, "packageB", "alpha", catalog)
 	subs := []*v1alpha1.Subscription{sub, newSub}
 
-	fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{
-		Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{
-			catalog: {
-				Key: catalog,
-				Operators: []*cache.Operator{
+	satResolver := SatResolver{
+		cache: cache.New(cache.StaticSourceProvider{
+			catalog: &cache.Snapshot{
+				Entries: []*cache.Operator{
 					genOperator("packageA.v1", "0.0.1", "", "packageA", "alpha", "community", "olm", nil, nil, nil, "", false),
 					genOperator("packageB.v1", "1.0.0", "", "packageB", "alpha", "community", "olm", nil, nil, nil, "", false),
 					genOperator("packageB.v1", "1.0.0", "", "packageB", "beta", "community", "olm", nil, nil, nil, "", false),
 				},
 			},
-		},
-	}
-	satResolver := SatResolver{
-		cache: getFakeOperatorCache(fakeNamespacedOperatorCache),
-		log:   logrus.New(),
+		}),
+		log: logrus.New(),
 	}
 
 	operators, err := satResolver.SolveOperators([]string{"olm"}, csvs, subs)
@@ -172,7 +157,7 @@ func TestSolveOperators_FindLatestVersion(t *testing.T) {
 	Provides := APISet
 
 	namespace := "olm"
-	catalog := registry.CatalogKey{"community", namespace}
+	catalog := cache.SourceKey{"community", namespace}
 
 	csv := existingOperator(namespace, "packageA.v1", "packageA", "alpha", "", Provides, nil, nil, nil)
 	csvs := []*v1alpha1.ClusterServiceVersion{csv}
@@ -180,28 +165,21 @@ func TestSolveOperators_FindLatestVersion(t *testing.T) {
 	newSub := newSub(namespace, "packageB", "alpha", catalog)
 	subs := []*v1alpha1.Subscription{sub, newSub}
 
-	fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{
-		Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{
-			registry.CatalogKey{
+	satResolver := SatResolver{
+		cache: cache.New(cache.StaticSourceProvider{
+			cache.SourceKey{
 				Namespace: "olm",
 				Name:      "community",
-			}: {
-				Key: registry.CatalogKey{
-					Namespace: "olm",
-					Name:      "community",
-				},
-				Operators: []*cache.Operator{
+			}: &cache.Snapshot{
+				Entries: []*cache.Operator{
 					genOperator("packageA.v1.0.1", "1.0.1", "packageA.v1", "packageA", "alpha", "community", "olm", nil, nil, nil, "", false),
 					genOperator("packageB.v0.9.0", "0.9.0", "", "packageB", "alpha", "community", "olm", nil, nil, nil, "", false),
 					genOperator("packageB.v1.0.0", "1.0.0", "packageB.v0.9.0", "packageB", "alpha", "community", "olm", nil, nil, nil, "", false),
 					genOperator("packageB.v1.0.1", "1.0.1", "packageB.v1.0.0", "packageB", "alpha", "community", "olm", nil, nil, nil, "", false),
 				},
 			},
-		},
-	}
-	satResolver := SatResolver{
-		cache: getFakeOperatorCache(fakeNamespacedOperatorCache),
-		log:   logrus.New(),
+		}),
+		log: logrus.New(),
 	}
 
 	operators, err := satResolver.SolveOperators([]string{"olm"}, csvs, subs)
@@ -226,7 +204,7 @@ func TestSolveOperators_FindLatestVersionWithDependencies(t *testing.T) {
 	Provides := APISet
 
 	namespace := "olm"
-	catalog := registry.CatalogKey{"community", namespace}
+	catalog := cache.SourceKey{"community", namespace}
 
 	csv := existingOperator(namespace, "packageA.v1", "packageA", "alpha", "", Provides, nil, nil, nil)
 	csvs := []*v1alpha1.ClusterServiceVersion{csv}
@@ -245,17 +223,10 @@ func TestSolveOperators_FindLatestVersionWithDependencies(t *testing.T) {
 		},
 	}
 
-	fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{
-		Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{
-			registry.CatalogKey{
-				Namespace: "olm",
-				Name:      "community",
-			}: {
-				Key: registry.CatalogKey{
-					Namespace: "olm",
-					Name:      "community",
-				},
-				Operators: []*cache.Operator{
+	satResolver := SatResolver{
+		cache: cache.New(cache.StaticSourceProvider{
+			catalog: &cache.Snapshot{
+				Entries: []*cache.Operator{
 					genOperator("packageA.v1.0.1", "1.0.1", "packageA.v1", "packageA", "alpha", "community", "olm", nil, nil, nil, "", false),
 					genOperator("packageB.v0.9.0", "0.9.0", "", "packageB", "alpha", "community", "olm", nil, nil, nil, "", false),
 					genOperator("packageB.v1.0.0", "1.0.0", "packageB.v0.9.0", "packageB", "alpha", "community", "olm", nil, nil, nil, "", false),
@@ -267,11 +238,8 @@ func TestSolveOperators_FindLatestVersionWithDependencies(t *testing.T) {
 					genOperator("packageD.v1.0.2", "1.0.2", "packageD.v1.0.1", "packageD", "alpha", "community", "olm", nil, nil, nil, "", false),
 				},
 			},
-		},
-	}
-	satResolver := SatResolver{
-		cache: getFakeOperatorCache(fakeNamespacedOperatorCache),
-		log:   logrus.New(),
+		}),
+		log: logrus.New(),
 	}
 
 	operators, err := satResolver.SolveOperators([]string{"olm"}, csvs, subs)
@@ -295,7 +263,7 @@ func TestSolveOperators_FindLatestVersionWithNestedDependencies(t *testing.T) {
 	Provides := APISet
 
 	namespace := "olm"
-	catalog := registry.CatalogKey{"community", namespace}
+	catalog := cache.SourceKey{"community", namespace}
 
 	csv := existingOperator(namespace, "packageA.v1", "packageA", "alpha", "", Provides, nil, nil, nil)
 	csvs := []*v1alpha1.ClusterServiceVersion{csv}
@@ -320,17 +288,10 @@ func TestSolveOperators_FindLatestVersionWithNestedDependencies(t *testing.T) {
 		},
 	}
 
-	fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{
-		Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{
-			registry.CatalogKey{
-				Namespace: "olm",
-				Name:      "community",
-			}: {
-				Key: registry.CatalogKey{
-					Namespace: "olm",
-					Name:      "community",
-				},
-				Operators: []*cache.Operator{
+	satResolver := SatResolver{
+		cache: cache.New(cache.StaticSourceProvider{
+			catalog: &cache.Snapshot{
+				Entries: []*cache.Operator{
 					genOperator("packageA.v1.0.1", "1.0.1", "packageA.v1", "packageA", "alpha", "community", "olm", nil, nil, nil, "", false),
 					genOperator("packageB.v0.9.0", "0.9.0", "", "packageB", "alpha", "community", "olm", nil, nil, nil, "", false),
 					genOperator("packageB.v1.0.0", "1.0.0", "packageB.v0.9.0", "packageB", "alpha", "community", "olm", nil, nil, nil, "", false),
@@ -342,11 +303,8 @@ func TestSolveOperators_FindLatestVersionWithNestedDependencies(t *testing.T) {
 					genOperator("packageE.v1.0.0", "1.0.0", "", "packageE", "alpha", "community", "olm", nil, nil, nil, "", false),
 				},
 			},
-		},
-	}
-	satResolver := SatResolver{
-		cache: getFakeOperatorCache(fakeNamespacedOperatorCache),
-		log:   logrus.New(),
+		}),
+		log: logrus.New(),
 	}
 
 	operators, err := satResolver.SolveOperators([]string{"olm"}, csvs, subs)
@@ -366,6 +324,40 @@ func TestSolveOperators_FindLatestVersionWithNestedDependencies(t *testing.T) {
 	}
 }
 
+type stubCatalogSourceLister struct {
+	catsrcs   []*v1alpha1.CatalogSource
+	namespace string
+}
+
+func (l *stubCatalogSourceLister) List(labels.Selector) ([]*v1alpha1.CatalogSource, error) {
+	if l.namespace == "" {
+		return l.catsrcs, nil
+	}
+	var result []*v1alpha1.CatalogSource
+	for _, cs := range l.catsrcs {
+		if cs.Namespace == l.namespace {
+			result = append(result, cs)
+		}
+	}
+	return result, nil
+}
+
+func (l *stubCatalogSourceLister) Get(name string) (*v1alpha1.CatalogSource, error) {
+	for _, cs := range l.catsrcs {
+		if cs.Name == name {
+			return cs, nil
+		}
+	}
+	return nil, errors.New("stub not found")
+}
+
+func (l *stubCatalogSourceLister) CatalogSources(namespace string) listersv1alpha1.CatalogSourceNamespaceLister {
+	return &stubCatalogSourceLister{
+		catsrcs:   l.catsrcs,
+		namespace: namespace,
+	}
+}
+
 func TestSolveOperators_CatsrcPrioritySorting(t *testing.T) {
 	opToAddVersionDeps := []*api.Dependency{
 		{
@@ -375,58 +367,45 @@ func TestSolveOperators_CatsrcPrioritySorting(t *testing.T) {
 	}
 
 	namespace := "olm"
-	customCatalog := registry.CatalogKey{"community", namespace}
+	customCatalog := cache.SourceKey{"community", namespace}
 	newSub := newSub(namespace, "packageA", "alpha", customCatalog)
 	subs := []*v1alpha1.Subscription{newSub}
 
-	fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{
-		Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{
-			registry.CatalogKey{
-				Namespace: "olm",
-				Name:      "community",
-			}: {
-				Key: registry.CatalogKey{
-					Namespace: "olm",
-					Name:      "community",
-				},
-				Operators: []*cache.Operator{
-					genOperator("packageA.v1", "0.0.1", "", "packageA", "alpha", "community", namespace, nil,
-						nil, opToAddVersionDeps, "", false),
-				},
+	ssp := cache.StaticSourceProvider{
+		cache.SourceKey{Namespace: "olm", Name: "community"}: &cache.Snapshot{
+			Entries: []*cache.Operator{
+				genOperator("packageA.v1", "0.0.1", "", "packageA", "alpha", "community", namespace, nil,
+					nil, opToAddVersionDeps, "", false),
 			},
-			registry.CatalogKey{
-				Namespace: "olm",
-				Name:      "community-operator",
-			}: {
-				Key: registry.CatalogKey{
-					Namespace: "olm",
-					Name:      "community-operator",
-				},
-				Operators: []*cache.Operator{
-					genOperator("packageB.v1", "0.0.1", "", "packageB", "alpha", "community-operator",
-						namespace, nil, nil, nil, "", false),
-				},
+		},
+		cache.SourceKey{Namespace: "olm", Name: "community-operator"}: &cache.Snapshot{
+			Entries: []*cache.Operator{
+				genOperator("packageB.v1", "0.0.1", "", "packageB", "alpha", "community-operator",
+					namespace, nil, nil, nil, "", false),
 			},
-			registry.CatalogKey{
-				Namespace: "olm",
-				Name:      "high-priority-operator",
-			}: {
-				Key: registry.CatalogKey{
-					Namespace: "olm",
-					Name:      "high-priority-operator",
-				},
-				Priority: 100,
-				Operators: []*cache.Operator{
-					genOperator("packageB.v1", "0.0.1", "", "packageB", "alpha", "high-priority-operator",
-						namespace, nil, nil, nil, "", false),
-				},
+		},
+		cache.SourceKey{Namespace: "olm", Name: "high-priority-operator"}: &cache.Snapshot{
+			Entries: []*cache.Operator{
+				genOperator("packageB.v1", "0.0.1", "", "packageB", "alpha", "high-priority-operator",
+					namespace, nil, nil, nil, "", false),
 			},
 		},
 	}
 
-	// operators sorted by priority.
 	satResolver := SatResolver{
-		cache: getFakeOperatorCache(fakeNamespacedOperatorCache),
+		cache: cache.New(ssp, cache.WithCatalogSourceLister(&stubCatalogSourceLister{
+			catsrcs: []*v1alpha1.CatalogSource{
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Namespace: "olm",
+						Name:      "high-priority-operator",
+					},
+					Spec: v1alpha1.CatalogSourceSpec{
+						Priority: 100,
+					},
+				},
+			},
+		})),
 	}
 
 	operators, err := satResolver.SolveOperators([]string{"olm"}, []*v1alpha1.ClusterServiceVersion{}, subs)
@@ -443,23 +422,39 @@ func TestSolveOperators_CatsrcPrioritySorting(t *testing.T) {
 	}
 
 	// Catsrc with the same priority, ns, different name
-	fakeNamespacedOperatorCache.Snapshots[registry.CatalogKey{
+	ssp[cache.SourceKey{
 		Namespace: "olm",
 		Name:      "community-operator",
-	}] = &cache.CatalogSnapshot{
-		Key: registry.CatalogKey{
-			Namespace: "olm",
-			Name:      "community-operator",
-		},
-		Priority: 100,
-		Operators: []*cache.Operator{
+	}] = &cache.Snapshot{
+		Entries: []*cache.Operator{
 			genOperator("packageB.v1", "0.0.1", "", "packageB", "alpha", "community-operator",
 				namespace, nil, nil, nil, "", false),
 		},
 	}
 
 	satResolver = SatResolver{
-		cache: getFakeOperatorCache(fakeNamespacedOperatorCache),
+		cache: cache.New(ssp, cache.WithCatalogSourceLister(&stubCatalogSourceLister{
+			catsrcs: []*v1alpha1.CatalogSource{
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Namespace: "olm",
+						Name:      "high-priority-operator",
+					},
+					Spec: v1alpha1.CatalogSourceSpec{
+						Priority: 100,
+					},
+				},
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Namespace: "olm",
+						Name:      "community-operator",
+					},
+					Spec: v1alpha1.CatalogSourceSpec{
+						Priority: 100,
+					},
+				},
+			},
+		})),
 	}
 
 	operators, err = satResolver.SolveOperators([]string{"olm"}, []*v1alpha1.ClusterServiceVersion{}, subs)
@@ -476,15 +471,11 @@ func TestSolveOperators_CatsrcPrioritySorting(t *testing.T) {
 	}
 
 	// operators from the same catalogs source should be prioritized.
-	fakeNamespacedOperatorCache.Snapshots[registry.CatalogKey{
+	ssp[cache.SourceKey{
 		Namespace: "olm",
 		Name:      "community",
-	}] = &cache.CatalogSnapshot{
-		Key: registry.CatalogKey{
-			Namespace: "olm",
-			Name:      "community",
-		},
-		Operators: []*cache.Operator{
+	}] = &cache.Snapshot{
+		Entries: []*cache.Operator{
 			genOperator("packageA.v1", "0.0.1", "", "packageA", "alpha", "community", namespace, nil,
 				nil, opToAddVersionDeps, "", false),
 			genOperator("packageB.v1", "0.0.1", "", "packageB", "alpha", "community",
@@ -493,7 +484,7 @@ func TestSolveOperators_CatsrcPrioritySorting(t *testing.T) {
 	}
 
 	satResolver = SatResolver{
-		cache: getFakeOperatorCache(fakeNamespacedOperatorCache),
+		cache: cache.New(ssp),
 	}
 
 	operators, err = satResolver.SolveOperators([]string{"olm"}, []*v1alpha1.ClusterServiceVersion{}, subs)
@@ -516,7 +507,7 @@ func TestSolveOperators_WithDependencies(t *testing.T) {
 	Provides := APISet
 
 	namespace := "olm"
-	catalog := registry.CatalogKey{"community", namespace}
+	catalog := cache.SourceKey{"community", namespace}
 
 	csv := existingOperator(namespace, "packageA.v1", "packageA", "alpha", "", Provides, nil, nil, nil)
 	csvs := []*v1alpha1.ClusterServiceVersion{csv}
@@ -531,27 +522,17 @@ func TestSolveOperators_WithDependencies(t *testing.T) {
 		},
 	}
 
-	fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{
-		Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{
-			registry.CatalogKey{
-				Namespace: "olm",
-				Name:      "community",
-			}: {
-				Key: registry.CatalogKey{
-					Namespace: "olm",
-					Name:      "community",
-				},
-				Operators: []*cache.Operator{
+	satResolver := SatResolver{
+		cache: cache.New(cache.StaticSourceProvider{
+			catalog: &cache.Snapshot{
+				Entries: []*cache.Operator{
 					genOperator("packageA.v1.0.1", "1.0.1", "packageA.v1", "packageA", "alpha", "community", "olm", nil, nil, nil, "", false),
 					genOperator("packageB.v1", "1.0.0", "", "packageB", "alpha", "community", "olm", nil, nil, opToAddVersionDeps, "", false),
 					genOperator("packageC.v1", "0.1.0", "", "packageC", "alpha", "community", "olm", nil, nil, nil, "", false),
 				},
 			},
-		},
-	}
-	satResolver := SatResolver{
-		cache: getFakeOperatorCache(fakeNamespacedOperatorCache),
-		log:   logrus.New(),
+		}),
+		log: logrus.New(),
 	}
 
 	operators, err := satResolver.SolveOperators([]string{"olm"}, csvs, subs)
@@ -574,7 +555,7 @@ func TestSolveOperators_WithGVKDependencies(t *testing.T) {
 	Provides := APISet
 
 	namespace := "olm"
-	community := registry.CatalogKey{"community", namespace}
+	community := cache.SourceKey{"community", namespace}
 
 	csvs := []*v1alpha1.ClusterServiceVersion{
 		existingOperator(namespace, "packageA.v1", "packageA", "alpha", "", nil, nil, nil, nil),
@@ -591,21 +572,17 @@ func TestSolveOperators_WithGVKDependencies(t *testing.T) {
 		},
 	}
 
-	fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{
-		Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{
-			community: {
-				Key: community,
-				Operators: []*cache.Operator{
+	satResolver := SatResolver{
+		cache: cache.New(cache.StaticSourceProvider{
+			community: &cache.Snapshot{
+				Entries: []*cache.Operator{
 					genOperator("packageA.v1", "0.0.1", "", "packageA", "alpha", "community", "olm", nil, nil, nil, "", false),
 					genOperator("packageB.v1", "1.0.0", "", "packageB", "alpha", "community", "olm", Provides, nil, deps, "", false),
 					genOperator("packageC.v1", "0.1.0", "", "packageC", "alpha", "community", "olm", nil, Provides, nil, "", false),
 				},
 			},
-		},
-	}
-	satResolver := SatResolver{
-		cache: getFakeOperatorCache(fakeNamespacedOperatorCache),
-		log:   logrus.New(),
+		}),
+		log: logrus.New(),
 	}
 
 	operators, err := satResolver.SolveOperators([]string{"olm"}, csvs, subs)
@@ -624,7 +601,7 @@ func TestSolveOperators_WithGVKDependencies(t *testing.T) {
 
 func TestSolveOperators_WithLabelDependencies(t *testing.T) {
 	namespace := "olm"
-	catalog := registry.CatalogKey{"community", namespace}
+	catalog := cache.SourceKey{"community", namespace}
 
 	newSub := newSub(namespace, "packageA", "alpha", catalog)
 	subs := []*v1alpha1.Subscription{newSub}
@@ -648,25 +625,15 @@ func TestSolveOperators_WithLabelDependencies(t *testing.T) {
 		operatorBv1.Properties = append(operatorBv1.Properties, p)
 	}
 
-	fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{
-		Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{
-			registry.CatalogKey{
-				Namespace: "olm",
-				Name:      "community",
-			}: {
-				Key: registry.CatalogKey{
-					Namespace: "olm",
-					Name:      "community",
-				},
-				Operators: []*cache.Operator{
+	satResolver := SatResolver{
+		cache: cache.New(cache.StaticSourceProvider{
+			catalog: &cache.Snapshot{
+				Entries: []*cache.Operator{
 					genOperator("packageA", "0.0.1", "", "packageA", "alpha", "community", "olm", nil, nil, deps, "", false),
 					operatorBv1,
 				},
 			},
-		},
-	}
-	satResolver := SatResolver{
-		cache: getFakeOperatorCache(fakeNamespacedOperatorCache),
+		}),
 	}
 
 	operators, err := satResolver.SolveOperators([]string{"olm"}, nil, subs)
@@ -685,7 +652,7 @@ func TestSolveOperators_WithLabelDependencies(t *testing.T) {
 
 func TestSolveOperators_WithUnsatisfiableLabelDependencies(t *testing.T) {
 	namespace := "olm"
-	catalog := registry.CatalogKey{"community", namespace}
+	catalog := cache.SourceKey{"community", namespace}
 
 	newSub := newSub(namespace, "packageA", "alpha", catalog)
 	subs := []*v1alpha1.Subscription{newSub}
@@ -697,25 +664,15 @@ func TestSolveOperators_WithUnsatisfiableLabelDependencies(t *testing.T) {
 		},
 	}
 
-	fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{
-		Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{
-			registry.CatalogKey{
-				Namespace: "olm",
-				Name:      "community",
-			}: {
-				Key: registry.CatalogKey{
-					Namespace: "olm",
-					Name:      "community",
-				},
-				Operators: []*cache.Operator{
+	satResolver := SatResolver{
+		cache: cache.New(cache.StaticSourceProvider{
+			catalog: &cache.Snapshot{
+				Entries: []*cache.Operator{
 					genOperator("packageA", "0.0.1", "", "packageA", "alpha", "community", "olm", nil, nil, deps, "", false),
 					genOperator("packageB.v1", "1.0.0", "", "packageB", "alpha", "community", "olm", nil, nil, nil, "", false),
 				},
 			},
-		},
-	}
-	satResolver := SatResolver{
-		cache: getFakeOperatorCache(fakeNamespacedOperatorCache),
+		}),
 	}
 
 	operators, err := satResolver.SolveOperators([]string{"olm"}, nil, subs)
@@ -728,7 +685,7 @@ func TestSolveOperators_WithNestedGVKDependencies(t *testing.T) {
 	Provides := APISet
 
 	namespace := "olm"
-	catalog := registry.CatalogKey{"community", namespace}
+	catalog := cache.SourceKey{"community", namespace}
 
 	csv := existingOperator(namespace, "packageA.v1", "packageA", "alpha", "", Provides, nil, nil, nil)
 	csvs := []*v1alpha1.ClusterServiceVersion{csv}
@@ -750,17 +707,13 @@ func TestSolveOperators_WithNestedGVKDependencies(t *testing.T) {
 		},
 	}
 
-	fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{
-		Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{
-			registry.CatalogKey{
+	satResolver := SatResolver{
+		cache: cache.New(cache.StaticSourceProvider{
+			cache.SourceKey{
 				Namespace: "olm",
 				Name:      "community",
-			}: {
-				Key: registry.CatalogKey{
-					Namespace: "olm",
-					Name:      "community",
-				},
-				Operators: []*cache.Operator{
+			}: &cache.Snapshot{
+				Entries: []*cache.Operator{
 					genOperator("packageA.v1.0.1", "1.0.1", "packageA.v1", "packageA", "alpha", "community", "olm", nil, nil, nil, "", false),
 					genOperator("packageB.v1.0.0", "1.0.0", "", "packageB", "alpha", "community", "olm", Provides, nil, deps, "", false),
 					genOperator("packageB.v1.0.1", "1.0.1", "packageB.v1.0.0", "packageB", "alpha", "community", "olm", Provides, nil, deps, "", false),
@@ -769,25 +722,18 @@ func TestSolveOperators_WithNestedGVKDependencies(t *testing.T) {
 					genOperator("packageD.v1.0.1", "1.0.1", "", "packageD", "alpha", "community", "olm", nil, Provides2, deps2, "", false),
 				},
 			},
-			registry.CatalogKey{
+			cache.SourceKey{
 				Namespace: "olm",
 				Name:      "certified",
-			}: {
-				Key: registry.CatalogKey{
-					Namespace: "olm",
-					Name:      "certified",
-				},
-				Operators: []*cache.Operator{
+			}: &cache.Snapshot{
+				Entries: []*cache.Operator{
 					genOperator("packageC.v1.0.0", "1.0.0", "", "packageC", "alpha", "certified", "olm", Provides2, Provides, deps2, "", false),
 					genOperator("packageC.v1.0.1", "1.0.1", "packageC.v1.0.0", "packageC", "alpha", "certified", "olm", Provides2, Provides, deps2, "", false),
 					genOperator("packageD.v1.0.1", "1.0.1", "", "packageD", "alpha", "certified", "olm", nil, Provides2, nil, "", false),
 				},
 			},
-		},
-	}
-	satResolver := SatResolver{
-		cache: getFakeOperatorCache(fakeNamespacedOperatorCache),
-		log:   logrus.New(),
+		}),
+		log: logrus.New(),
 	}
 
 	operators, err := satResolver.SolveOperators([]string{"olm"}, csvs, subs)
@@ -815,7 +761,7 @@ func TestSolveOperators_IgnoreUnsatisfiableDependencies(t *testing.T) {
 	const namespace = "olm"
 
 	Provides := cache.APISet{opregistry.APIKey{Group: "g", Version: "v", Kind: "k", Plural: "ks"}: struct{}{}}
-	community := registry.CatalogKey{Name: "community", Namespace: namespace}
+	community := cache.SourceKey{Name: "community", Namespace: namespace}
 	csvs := []*v1alpha1.ClusterServiceVersion{
 		existingOperator(namespace, "packageA.v1", "packageA", "alpha", "", Provides, nil, nil, nil),
 	}
@@ -837,35 +783,24 @@ func TestSolveOperators_IgnoreUnsatisfiableDependencies(t *testing.T) {
 		},
 	}
 
-	fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{
-		Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{
-			community: {
-				Key: community,
-				Operators: []*cache.Operator{
+	satResolver := SatResolver{
+		cache: cache.New(cache.StaticSourceProvider{
+			community: &cache.Snapshot{
+				Entries: []*cache.Operator{
 					genOperator("packageA.v1", "0.0.1", "", "packageA", "alpha", "community", "olm", nil, nil, nil, "", false),
 					genOperator("packageB.v1", "1.0.0", "", "packageB", "alpha", "community", "olm", nil, nil, opToAddVersionDeps, "", false),
 					genOperator("packageC.v1", "0.1.0", "", "packageC", "alpha", "community", "olm", nil, nil, unsatisfiableVersionDeps, "", false),
 				},
 			},
-			{
-				Namespace: "olm",
-				Name:      "certified",
-			}: {
-				Key: registry.CatalogKey{
-					Namespace: "olm",
-					Name:      "certified",
-				},
-				Operators: []*cache.Operator{
+			{Namespace: "olm", Name: "certified"}: &cache.Snapshot{
+				Entries: []*cache.Operator{
 					genOperator("packageA.v1", "0.0.1", "", "packageA", "alpha", "certified", "olm", nil, nil, nil, "", false),
 					genOperator("packageB.v1", "1.0.0", "", "packageB", "alpha", "certified", "olm", nil, nil, opToAddVersionDeps, "", false),
 					genOperator("packageC.v1", "0.1.0", "", "packageC", "alpha", "certified", "olm", nil, nil, nil, "", false),
 				},
 			},
-		},
-	}
-	satResolver := SatResolver{
-		cache: getFakeOperatorCache(fakeNamespacedOperatorCache),
-		log:   logrus.New(),
+		}),
+		log: logrus.New(),
 	}
 
 	operators, err := satResolver.SolveOperators([]string{"olm"}, csvs, subs)
@@ -889,32 +824,28 @@ func TestSolveOperators_PreferCatalogInSameNamespace(t *testing.T) {
 
 	namespace := "olm"
 	altNamespace := "alt-olm"
-	catalog := registry.CatalogKey{"community", namespace}
-	altnsCatalog := registry.CatalogKey{"alt-community", altNamespace}
+	catalog := cache.SourceKey{"community", namespace}
+	altnsCatalog := cache.SourceKey{"alt-community", altNamespace}
 
 	csv := existingOperator(namespace, "packageA.v1", "packageA", "alpha", "", Provides, nil, nil, nil)
 	csvs := []*v1alpha1.ClusterServiceVersion{csv}
 	sub := existingSub(namespace, "packageA.v1", "packageA", "alpha", catalog)
 	subs := []*v1alpha1.Subscription{sub}
 
-	fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{
-		Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{
-			catalog: {
-				Operators: []*cache.Operator{
+	satResolver := SatResolver{
+		cache: cache.New(cache.StaticSourceProvider{
+			catalog: &cache.Snapshot{
+				Entries: []*cache.Operator{
 					genOperator("packageA.v0.0.1", "0.0.1", "packageA.v1", "packageA", "alpha", catalog.Name, catalog.Namespace, nil, Provides, nil, "", false),
 				},
 			},
-			altnsCatalog: {
-				Operators: []*cache.Operator{
+			altnsCatalog: &cache.Snapshot{
+				Entries: []*cache.Operator{
 					genOperator("packageA.v0.0.1", "0.0.1", "packageA.v1", "packageA", "alpha", altnsCatalog.Name, altnsCatalog.Namespace, nil, Provides, nil, "", false),
 				},
 			},
-		},
-		Namespaces: []string{namespace, altNamespace},
-	}
-	satResolver := SatResolver{
-		cache: getFakeOperatorCache(fakeNamespacedOperatorCache),
-		log:   logrus.New(),
+		}),
+		log: logrus.New(),
 	}
 
 	operators, err := satResolver.SolveOperators([]string{namespace}, csvs, subs)
@@ -933,27 +864,23 @@ func TestSolveOperators_ResolveOnlyInCachedNamespaces(t *testing.T) {
 	Provides := APISet
 
 	namespace := "olm"
-	catalog := registry.CatalogKey{"community", namespace}
-	otherCatalog := registry.CatalogKey{Name: "secret", Namespace: "secret"}
+	catalog := cache.SourceKey{"community", namespace}
+	otherCatalog := cache.SourceKey{Name: "secret", Namespace: "secret"}
 
 	csv := existingOperator(namespace, "packageA.v1", "packageA", "alpha", "", Provides, nil, nil, nil)
 	csvs := []*v1alpha1.ClusterServiceVersion{csv}
 	newSub := newSub(namespace, "packageA", "alpha", catalog)
 	subs := []*v1alpha1.Subscription{newSub}
 
-	fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{
-		Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{
-			catalog: {
-				Operators: []*cache.Operator{
+	satResolver := SatResolver{
+		cache: cache.New(cache.StaticSourceProvider{
+			catalog: &cache.Snapshot{
+				Entries: []*cache.Operator{
 					genOperator("packageA.v0.0.1", "0.0.1", "packageA.v1", "packageA", "alpha", otherCatalog.Name, otherCatalog.Namespace, nil, Provides, nil, "", false),
 				},
 			},
-		},
-		Namespaces: []string{otherCatalog.Namespace},
-	}
-	satResolver := SatResolver{
-		cache: getFakeOperatorCache(fakeNamespacedOperatorCache),
-		log:   logrus.New(),
+		}),
+		log: logrus.New(),
 	}
 
 	operators, err := satResolver.SolveOperators([]string{namespace}, csvs, subs)
@@ -968,7 +895,7 @@ func TestSolveOperators_PreferDefaultChannelInResolution(t *testing.T) {
 	Provides := APISet
 
 	namespace := "olm"
-	catalog := registry.CatalogKey{Name: "community", Namespace: namespace}
+	catalog := cache.SourceKey{Name: "community", Namespace: namespace}
 
 	csvs := []*v1alpha1.ClusterServiceVersion{}
 
@@ -977,21 +904,17 @@ func TestSolveOperators_PreferDefaultChannelInResolution(t *testing.T) {
 	newSub := newSub(namespace, "packageA", "", catalog)
 	subs := []*v1alpha1.Subscription{newSub}
 
-	fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{
-		Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{
-			catalog: {
-				Operators: []*cache.Operator{
+	satResolver := SatResolver{
+		cache: cache.New(cache.StaticSourceProvider{
+			catalog: &cache.Snapshot{
+				Entries: []*cache.Operator{
 					// Default channel is stable in this case
 					genOperator("packageA.v0.0.2", "0.0.2", "packageA.v1", "packageA", "alpha", catalog.Name, catalog.Namespace, nil, Provides, nil, defaultChannel, false),
 					genOperator("packageA.v0.0.1", "0.0.1", "packageA.v1", "packageA", "stable", catalog.Name, catalog.Namespace, nil, Provides, nil, defaultChannel, false),
 				},
 			},
-		},
-	}
-
-	satResolver := SatResolver{
-		cache: getFakeOperatorCache(fakeNamespacedOperatorCache),
-		log:   logrus.New(),
+		}),
+		log: logrus.New(),
 	}
 
 	operators, err := satResolver.SolveOperators([]string{namespace}, csvs, subs)
@@ -1010,7 +933,7 @@ func TestSolveOperators_PreferDefaultChannelInResolutionForTransitiveDependencie
 	Provides := APISet
 
 	namespace := "olm"
-	catalog := registry.CatalogKey{Name: "community", Namespace: namespace}
+	catalog := cache.SourceKey{Name: "community", Namespace: namespace}
 
 	csvs := []*v1alpha1.ClusterServiceVersion{}
 
@@ -1018,21 +941,18 @@ func TestSolveOperators_PreferDefaultChannelInResolutionForTransitiveDependencie
 	subs := []*v1alpha1.Subscription{newSub}
 
 	const defaultChannel = "stable"
-	fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{
-		Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{
-			catalog: {
-				Operators: []*cache.Operator{
+
+	satResolver := SatResolver{
+		cache: cache.New(cache.StaticSourceProvider{
+			catalog: &cache.Snapshot{
+				Entries: []*cache.Operator{
 					genOperator("packageA.v0.0.1", "0.0.1", "packageA.v1", "packageA", "alpha", catalog.Name, catalog.Namespace, Provides, nil, cache.APISetToDependencies(nil, Provides), defaultChannel, false),
 					genOperator("packageB.v0.0.1", "0.0.1", "packageB.v1", "packageB", defaultChannel, catalog.Name, catalog.Namespace, nil, Provides, nil, defaultChannel, false),
 					genOperator("packageB.v0.0.2", "0.0.2", "packageB.v0.0.1", "packageB", "alpha", catalog.Name, catalog.Namespace, nil, Provides, nil, defaultChannel, false),
 				},
 			},
-		},
-	}
-
-	satResolver := SatResolver{
-		cache: getFakeOperatorCache(fakeNamespacedOperatorCache),
-		log:   logrus.New(),
+		}),
+		log: logrus.New(),
 	}
 
 	operators, err := satResolver.SolveOperators([]string{namespace}, csvs, subs)
@@ -1051,7 +971,7 @@ func TestSolveOperators_SubscriptionlessOperatorsSatisfyDependencies(t *testing.
 	Provides := APISet
 
 	namespace := "olm"
-	catalog := registry.CatalogKey{"community", namespace}
+	catalog := cache.SourceKey{"community", namespace}
 
 	csv := existingOperator(namespace, "packageA.v1", "packageA", "alpha", "", Provides, nil, nil, nil)
 	csvs := []*v1alpha1.ClusterServiceVersion{csv}
@@ -1065,26 +985,16 @@ func TestSolveOperators_SubscriptionlessOperatorsSatisfyDependencies(t *testing.
 		},
 	}
 
-	fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{
-		Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{
-			registry.CatalogKey{
-				Namespace: "olm",
-				Name:      "community",
-			}: {
-				Key: registry.CatalogKey{
-					Namespace: "olm",
-					Name:      "community",
-				},
-				Operators: []*cache.Operator{
+	satResolver := SatResolver{
+		cache: cache.New(cache.StaticSourceProvider{
+			catalog: &cache.Snapshot{
+				Entries: []*cache.Operator{
 					genOperator("packageB.v1.0.0", "1.0.0", "", "packageB", "alpha", "community", "olm", Provides, nil, deps, "", false),
 					genOperator("packageB.v1.0.1", "1.0.1", "packageB.v1.0.0", "packageB", "alpha", "community", "olm", Provides, nil, deps, "", false),
 				},
 			},
-		},
-	}
-	satResolver := SatResolver{
-		cache: getFakeOperatorCache(fakeNamespacedOperatorCache),
-		log:   logrus.New(),
+		}),
+		log: logrus.New(),
 	}
 
 	operators, err := satResolver.SolveOperators([]string{"olm"}, csvs, subs)
@@ -1104,33 +1014,23 @@ func TestSolveOperators_SubscriptionlessOperatorsCanConflict(t *testing.T) {
 	Provides := APISet
 
 	namespace := "olm"
-	catalog := registry.CatalogKey{"community", namespace}
+	catalog := cache.SourceKey{"community", namespace}
 
 	csv := existingOperator(namespace, "packageA.v1", "packageA", "alpha", "", Provides, nil, nil, nil)
 	csvs := []*v1alpha1.ClusterServiceVersion{csv}
 	newSub := newSub(namespace, "packageB", "alpha", catalog)
 	subs := []*v1alpha1.Subscription{newSub}
 
-	fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{
-		Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{
-			registry.CatalogKey{
-				Namespace: "olm",
-				Name:      "community",
-			}: {
-				Key: registry.CatalogKey{
-					Namespace: "olm",
-					Name:      "community",
-				},
-				Operators: []*cache.Operator{
+	satResolver := SatResolver{
+		cache: cache.New(cache.StaticSourceProvider{
+			catalog: &cache.Snapshot{
+				Entries: []*cache.Operator{
 					genOperator("packageB.v1.0.0", "1.0.0", "", "packageB", "alpha", "community", "olm", nil, Provides, nil, "", false),
 					genOperator("packageB.v1.0.1", "1.0.1", "packageB.v1.0.0", "packageB", "alpha", "community", "olm", nil, Provides, nil, "", false),
 				},
 			},
-		},
-	}
-	satResolver := SatResolver{
-		cache: getFakeOperatorCache(fakeNamespacedOperatorCache),
-		log:   logrus.New(),
+		}),
+		log: logrus.New(),
 	}
 
 	_, err := satResolver.SolveOperators([]string{"olm"}, csvs, subs)
@@ -1146,17 +1046,16 @@ func TestSolveOperators_PackageCannotSelfSatisfy(t *testing.T) {
 	RequiresBoth := Requires1.Union(Requires2)
 
 	namespace := "olm"
-	catalog := registry.CatalogKey{Name: "community", Namespace: namespace}
-	secondaryCatalog := registry.CatalogKey{Namespace: "olm", Name: "secondary"}
+	catalog := cache.SourceKey{Name: "community", Namespace: namespace}
+	secondaryCatalog := cache.SourceKey{Namespace: "olm", Name: "secondary"}
 
 	newSub := newSub(namespace, "packageA", "stable", catalog)
 	subs := []*v1alpha1.Subscription{newSub}
 
-	fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{
-		Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{
-			catalog: {
-				Key: catalog,
-				Operators: []*cache.Operator{
+	satResolver := SatResolver{
+		cache: cache.New(cache.StaticSourceProvider{
+			catalog: &cache.Snapshot{
+				Entries: []*cache.Operator{
 					genOperator("opA.v1.0.0", "1.0.0", "", "packageA", "stable", catalog.Name, catalog.Namespace, RequiresBoth, nil, nil, "", false),
 					// Despite satisfying dependencies of opA, this is not chosen because it is in the same package
 					genOperator("opABC.v1.0.0", "1.0.0", "", "packageA", "alpha", catalog.Name, catalog.Namespace, nil, ProvidesBoth, nil, "", false),
@@ -1165,19 +1064,15 @@ func TestSolveOperators_PackageCannotSelfSatisfy(t *testing.T) {
 					genOperator("opD.v1.0.0", "1.0.0", "", "packageB", "alpha", catalog.Name, catalog.Namespace, nil, Provides1, nil, "stable", false),
 				},
 			},
-			secondaryCatalog: {
-				Key: secondaryCatalog,
-				Operators: []*cache.Operator{
+			secondaryCatalog: &cache.Snapshot{
+				Entries: []*cache.Operator{
 					genOperator("opC.v1.0.0", "1.0.0", "", "packageB", "stable", secondaryCatalog.Name, secondaryCatalog.Namespace, nil, Provides2, nil, "stable", false),
 
 					genOperator("opE.v1.0.0", "1.0.0", "", "packageC", "stable", secondaryCatalog.Name, secondaryCatalog.Namespace, nil, Provides2, nil, "", false),
 				},
 			},
-		},
-	}
-	satResolver := SatResolver{
-		cache: getFakeOperatorCache(fakeNamespacedOperatorCache),
-		log:   logrus.New(),
+		}),
+		log: logrus.New(),
 	}
 
 	operators, err := satResolver.SolveOperators([]string{"olm"}, nil, subs)
@@ -1201,18 +1096,17 @@ func TestSolveOperators_TransferApiOwnership(t *testing.T) {
 	ProvidesBoth := Provides1.Union(Provides2)
 
 	namespace := "olm"
-	catalog := registry.CatalogKey{Name: "community", Namespace: namespace}
+	catalog := cache.SourceKey{Name: "community", Namespace: namespace}
 
 	phases := []struct {
 		subs     []*v1alpha1.Subscription
-		catalog  *cache.CatalogSnapshot
+		catalog  cache.Source
 		expected cache.OperatorSet
 	}{
 		{
 			subs: []*v1alpha1.Subscription{newSub(namespace, "packageB", "stable", catalog)},
-			catalog: &cache.CatalogSnapshot{
-				Key: catalog,
-				Operators: []*cache.Operator{
+			catalog: &cache.Snapshot{
+				Entries: []*cache.Operator{
 					genOperator("opA.v1.0.0", "1.0.0", "", "packageA", "stable", catalog.Name, catalog.Namespace, nil, Provides1, nil, "", false),
 					genOperator("opB.v1.0.0", "1.0.0", "", "packageB", "stable", catalog.Name, catalog.Namespace, Requires1, Provides2, nil, "stable", false),
 				},
@@ -1228,9 +1122,8 @@ func TestSolveOperators_TransferApiOwnership(t *testing.T) {
 				existingSub(namespace, "opA.v1.0.0", "packageA", "stable", catalog),
 				existingSub(namespace, "opB.v1.0.0", "packageB", "stable", catalog),
 			},
-			catalog: &cache.CatalogSnapshot{
-				Key: catalog,
-				Operators: []*cache.Operator{
+			catalog: &cache.Snapshot{
+				Entries: []*cache.Operator{
 					genOperator("opA.v1.0.0", "1.0.0", "", "packageA", "stable", catalog.Name, catalog.Namespace, nil, Provides1, nil, "", false),
 					genOperator("opA.v1.0.1", "1.0.1", "opA.v1.0.0", "packageA", "stable", catalog.Name, catalog.Namespace, Requires1, nil, nil, "", false),
 					genOperator("opB.v1.0.0", "1.0.0", "", "packageB", "stable", catalog.Name, catalog.Namespace, Requires1, Provides2, nil, "stable", false),
@@ -1245,9 +1138,8 @@ func TestSolveOperators_TransferApiOwnership(t *testing.T) {
 				existingSub(namespace, "opA.v1.0.0", "packageA", "stable", catalog),
 				existingSub(namespace, "opB.v1.0.0", "packageB", "stable", catalog),
 			},
-			catalog: &cache.CatalogSnapshot{
-				Key: catalog,
-				Operators: []*cache.Operator{
+			catalog: &cache.Snapshot{
+				Entries: []*cache.Operator{
 					genOperator("opA.v1.0.0", "1.0.0", "", "packageA", "stable", catalog.Name, catalog.Namespace, nil, Provides1, nil, "", false),
 					genOperator("opA.v1.0.1", "1.0.1", "opA.v1.0.0", "packageA", "stable", catalog.Name, catalog.Namespace, Requires1, nil, nil, "", false),
 					genOperator("opB.v1.0.0", "1.0.0", "", "packageB", "stable", catalog.Name, catalog.Namespace, Requires1, Provides2, nil, "stable", false),
@@ -1264,14 +1156,11 @@ func TestSolveOperators_TransferApiOwnership(t *testing.T) {
 	var operators cache.OperatorSet
 	for i, p := range phases {
 		t.Run(fmt.Sprintf("phase %d", i+1), func(t *testing.T) {
-			fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{
-				Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{
-					catalog: p.catalog,
-				},
-			}
 			satResolver := SatResolver{
-				cache: getFakeOperatorCache(fakeNamespacedOperatorCache),
-				log:   logrus.New(),
+				cache: cache.New(cache.StaticSourceProvider{
+					catalog: p.catalog,
+				}),
+				log: logrus.New(),
 			}
 			csvs := make([]*v1alpha1.ClusterServiceVersion, 0)
 			for _, o := range operators {
@@ -1295,24 +1184,6 @@ func TestSolveOperators_TransferApiOwnership(t *testing.T) {
 	}
 }
 
-type FakeOperatorCache struct {
-	fakedNamespacedOperatorCache cache.NamespacedOperatorCache
-}
-
-func (f *FakeOperatorCache) Namespaced(namespaces ...string) cache.MultiCatalogOperatorFinder {
-	return &f.fakedNamespacedOperatorCache
-}
-
-func (f *FakeOperatorCache) Expire(key registry.CatalogKey) {
-	return
-}
-
-func getFakeOperatorCache(fakedNamespacedOperatorCache cache.NamespacedOperatorCache) cache.OperatorCacheProvider {
-	return &FakeOperatorCache{
-		fakedNamespacedOperatorCache: fakedNamespacedOperatorCache,
-	}
-}
-
 func genOperator(name, version, replaces, pkg, channel, catalogName, catalogNamespace string, requiredAPIs, providedAPIs cache.APISet, dependencies []*api.Dependency, defaultChannel string, deprecated bool) *cache.Operator {
 	semversion, _ := semver.Make(version)
 	properties := cache.APISetToProperties(providedAPIs, nil, deprecated)
@@ -1341,7 +1212,7 @@ func genOperator(name, version, replaces, pkg, channel, catalogName, catalogName
 		},
 		Properties: properties,
 		SourceInfo: &cache.OperatorSourceInfo{
-			Catalog: registry.CatalogKey{
+			Catalog: cache.SourceKey{
 				Name:      catalogName,
 				Namespace: catalogNamespace,
 			},
@@ -1352,7 +1223,7 @@ func genOperator(name, version, replaces, pkg, channel, catalogName, catalogName
 		ProvidedAPIs: providedAPIs,
 		RequiredAPIs: requiredAPIs,
 	}
-	cache.EnsurePackageProperty(o, pkg, version)
+	EnsurePackageProperty(o, pkg, version)
 	return o
 }
 
@@ -1362,25 +1233,21 @@ func stripBundle(o *cache.Operator) *cache.Operator {
 }
 
 func TestSolveOperators_WithoutDeprecated(t *testing.T) {
-	catalog := registry.CatalogKey{Name: "catalog", Namespace: "namespace"}
+	catalog := cache.SourceKey{Name: "catalog", Namespace: "namespace"}
 
 	subs := []*v1alpha1.Subscription{
 		newSub(catalog.Namespace, "packageA", "alpha", catalog),
 	}
 
-	fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{
-		Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{
-			catalog: {
-				Key: catalog,
-				Operators: []*cache.Operator{
+	satResolver := SatResolver{
+		cache: cache.New(cache.StaticSourceProvider{
+			catalog: &cache.Snapshot{
+				Entries: []*cache.Operator{
 					genOperator("packageA.v1", "0.0.1", "", "packageA", "alpha", catalog.Name, catalog.Namespace, nil, nil, nil, "", true),
 				},
 			},
-		},
-	}
-	satResolver := SatResolver{
-		cache: getFakeOperatorCache(fakeNamespacedOperatorCache),
-		log:   logrus.New(),
+		}),
+		log: logrus.New(),
 	}
 
 	operators, err := satResolver.SolveOperators([]string{catalog.Namespace}, nil, subs)
@@ -1389,22 +1256,19 @@ func TestSolveOperators_WithoutDeprecated(t *testing.T) {
 }
 
 func TestSolveOperatorsWithDeprecatedInnerChannelEntry(t *testing.T) {
-	catalog := registry.CatalogKey{Name: "catalog", Namespace: "namespace"}
+	catalog := cache.SourceKey{Name: "catalog", Namespace: "namespace"}
 
 	subs := []*v1alpha1.Subscription{
 		newSub(catalog.Namespace, "a", "c", catalog),
 	}
 	logger, _ := test.NewNullLogger()
 	resolver := SatResolver{
-		cache: getFakeOperatorCache(cache.NamespacedOperatorCache{
-			Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{
-				catalog: {
-					Key: catalog,
-					Operators: []*cache.Operator{
-						genOperator("a-1", "1.0.0", "", "a", "c", catalog.Name, catalog.Namespace, nil, nil, nil, "", false),
-						genOperator("a-2", "2.0.0", "a-1", "a", "c", catalog.Name, catalog.Namespace, nil, nil, nil, "", true),
-						genOperator("a-3", "3.0.0", "a-2", "a", "c", catalog.Name, catalog.Namespace, nil, nil, nil, "", false),
-					},
+		cache: cache.New(cache.StaticSourceProvider{
+			catalog: &cache.Snapshot{
+				Entries: []*cache.Operator{
+					genOperator("a-1", "1.0.0", "", "a", "c", catalog.Name, catalog.Namespace, nil, nil, nil, "", false),
+					genOperator("a-2", "2.0.0", "a-1", "a", "c", catalog.Name, catalog.Namespace, nil, nil, nil, "", true),
+					genOperator("a-3", "3.0.0", "a-2", "a", "c", catalog.Name, catalog.Namespace, nil, nil, nil, "", false),
 				},
 			},
 		}),
@@ -1422,7 +1286,7 @@ func TestSolveOperators_WithSkipsAndStartingCSV(t *testing.T) {
 	Provides := APISet
 
 	namespace := "olm"
-	catalog := registry.CatalogKey{"community", namespace}
+	catalog := cache.SourceKey{"community", namespace}
 
 	newSub := newSub(namespace, "packageB", "alpha", catalog, withStartingCSV("packageB.v1"))
 	subs := []*v1alpha1.Subscription{newSub}
@@ -1446,25 +1310,15 @@ func TestSolveOperators_WithSkipsAndStartingCSV(t *testing.T) {
 	op5.Skips = []string{"packageA.v2", "packageA.v3", "packageA.v4"}
 	op6 := genOperator("packageA.v6", "6.0.0", "packageA.v5", "packageA", "alpha", "community", "olm", nil, Provides, nil, "", false)
 
-	fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{
-		Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{
-			registry.CatalogKey{
-				Namespace: "olm",
-				Name:      "community",
-			}: {
-				Key: registry.CatalogKey{
-					Namespace: "olm",
-					Name:      "community",
-				},
-				Operators: []*cache.Operator{
+	satResolver := SatResolver{
+		cache: cache.New(cache.StaticSourceProvider{
+			catalog: &cache.Snapshot{
+				Entries: []*cache.Operator{
 					opB, opB2, op1, op2, op3, op4, op5, op6,
 				},
 			},
-		},
-	}
-	satResolver := SatResolver{
-		cache: getFakeOperatorCache(fakeNamespacedOperatorCache),
-		log:   logrus.New(),
+		}),
+		log: logrus.New(),
 	}
 
 	operators, err := satResolver.SolveOperators([]string{"olm"}, nil, subs)
@@ -1479,7 +1333,7 @@ func TestSolveOperators_WithSkipsAndStartingCSV(t *testing.T) {
 
 func TestSolveOperators_WithSkips(t *testing.T) {
 	const namespace = "test-namespace"
-	catalog := registry.CatalogKey{Name: "test-catalog", Namespace: namespace}
+	catalog := cache.SourceKey{Name: "test-catalog", Namespace: namespace}
 
 	newSub := newSub(namespace, "packageB", "alpha", catalog)
 	subs := []*v1alpha1.Subscription{newSub}
@@ -1488,19 +1342,15 @@ func TestSolveOperators_WithSkips(t *testing.T) {
 	opB2 := genOperator("packageB.v2", "2.0.0", "", "packageB", "alpha", catalog.Name, catalog.Namespace, nil, nil, nil, "", false)
 	opB2.Skips = []string{"packageB.v1"}
 
-	fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{
-		Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{
-			catalog: {
-				Key: catalog,
-				Operators: []*cache.Operator{
+	satResolver := SatResolver{
+		cache: cache.New(cache.StaticSourceProvider{
+			catalog: &cache.Snapshot{
+				Entries: []*cache.Operator{
 					opB, opB2,
 				},
 			},
-		},
-	}
-	satResolver := SatResolver{
-		cache: getFakeOperatorCache(fakeNamespacedOperatorCache),
-		log:   logrus.New(),
+		}),
+		log: logrus.New(),
 	}
 
 	operators, err := satResolver.SolveOperators([]string{namespace}, nil, subs)
@@ -1513,7 +1363,7 @@ func TestSolveOperators_WithSkips(t *testing.T) {
 
 func TestSolveOperatorsWithSkipsPreventingSelection(t *testing.T) {
 	const namespace = "test-namespace"
-	catalog := registry.CatalogKey{Name: "test-catalog", Namespace: namespace}
+	catalog := cache.SourceKey{Name: "test-catalog", Namespace: namespace}
 	gvks := cache.APISet{opregistry.APIKey{Group: "g", Version: "v", Kind: "k", Plural: "ks"}: struct{}{}}
 
 	// Subscription candidate a-1 requires a GVK provided
@@ -1528,12 +1378,9 @@ func TestSolveOperatorsWithSkipsPreventingSelection(t *testing.T) {
 
 	logger, _ := test.NewNullLogger()
 	satResolver := SatResolver{
-		cache: getFakeOperatorCache(cache.NamespacedOperatorCache{
-			Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{
-				catalog: {
-					Key:       catalog,
-					Operators: []*cache.Operator{a1, b3, b2, b1},
-				},
+		cache: cache.New(cache.StaticSourceProvider{
+			catalog: &cache.Snapshot{
+				Entries: []*cache.Operator{a1, b3, b2, b1},
 			},
 		}),
 		log: logger,
@@ -1545,7 +1392,7 @@ func TestSolveOperatorsWithSkipsPreventingSelection(t *testing.T) {
 
 func TestSolveOperatorsWithClusterServiceVersionHavingDependency(t *testing.T) {
 	const namespace = "test-namespace"
-	catalog := registry.CatalogKey{Name: "test-catalog", Namespace: namespace}
+	catalog := cache.SourceKey{Name: "test-catalog", Namespace: namespace}
 
 	a1 := existingOperator(namespace, "a-1", "a", "default", "", nil, nil, nil, nil)
 	a1.Annotations = map[string]string{
@@ -1564,13 +1411,10 @@ func TestSolveOperatorsWithClusterServiceVersionHavingDependency(t *testing.T) {
 
 	log, _ := test.NewNullLogger()
 	r := SatResolver{
-		cache: getFakeOperatorCache(cache.NamespacedOperatorCache{
-			Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{
-				catalog: {
-					Key: catalog,
-					Operators: []*cache.Operator{
-						genOperator("b-2", "2.0.0", "b-1", "b", "default", catalog.Name, catalog.Namespace, nil, nil, nil, "", false),
-					},
+		cache: cache.New(cache.StaticSourceProvider{
+			catalog: &cache.Snapshot{
+				Entries: []*cache.Operator{
+					genOperator("b-2", "2.0.0", "b-1", "b", "default", catalog.Name, catalog.Namespace, nil, nil, nil, "", false),
 				},
 			},
 		}),
@@ -1583,11 +1427,11 @@ func TestSolveOperatorsWithClusterServiceVersionHavingDependency(t *testing.T) {
 }
 
 func TestInferProperties(t *testing.T) {
-	catalog := registry.CatalogKey{Namespace: "namespace", Name: "name"}
+	catalog := cache.SourceKey{Namespace: "namespace", Name: "name"}
 
 	for _, tc := range []struct {
 		Name          string
-		Cache         cache.NamespacedOperatorCache
+		Cache         cache.StaticSourceProvider
 		CSV           *v1alpha1.ClusterServiceVersion
 		Subscriptions []*v1alpha1.Subscription
 		Expected      []*api.Property
@@ -1664,16 +1508,13 @@ func TestInferProperties(t *testing.T) {
 		},
 		{
 			Name: "one matching subscription infers package property",
-			Cache: cache.NamespacedOperatorCache{
-				Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{
-					catalog: {
-						Key: catalog,
-						Operators: []*cache.Operator{
-							{
-								Name: "a",
-								Bundle: &api.Bundle{
-									PackageName: "x",
-								},
+			Cache: cache.StaticSourceProvider{
+				catalog: &cache.Snapshot{
+					Entries: []*cache.Operator{
+						{
+							Name: "a",
+							Bundle: &api.Bundle{
+								PackageName: "x",
 							},
 						},
 					},
@@ -1706,6 +1547,47 @@ func TestInferProperties(t *testing.T) {
 				},
 			},
 		},
+		{
+			Name: "one matching subscription to other-namespace catalogsource infers package property",
+			Cache: cache.StaticSourceProvider{
+				{Namespace: "other-namespace", Name: "other-name"}: &cache.Snapshot{
+					Entries: []*cache.Operator{
+						{
+							Name: "a",
+							Bundle: &api.Bundle{
+								PackageName: "x",
+							},
+						},
+					},
+				},
+			},
+			CSV: &v1alpha1.ClusterServiceVersion{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "a",
+				},
+				Spec: v1alpha1.ClusterServiceVersionSpec{
+					Version: version.OperatorVersion{Version: semver.MustParse("1.2.3")},
+				},
+			},
+			Subscriptions: []*v1alpha1.Subscription{
+				{
+					Spec: &v1alpha1.SubscriptionSpec{
+						Package:                "x",
+						CatalogSource:          "other-name",
+						CatalogSourceNamespace: "other-namespace",
+					},
+					Status: v1alpha1.SubscriptionStatus{
+						InstalledCSV: "a",
+					},
+				},
+			},
+			Expected: []*api.Property{
+				{
+					Type:  "olm.package",
+					Value: `{"packageName":"x","version":"1.2.3"}`,
+				},
+			},
+		},
 		{
 			Name: "one matching subscription without catalog entry infers no properties",
 			CSV: &v1alpha1.ClusterServiceVersion{
@@ -1729,16 +1611,13 @@ func TestInferProperties(t *testing.T) {
 		},
 		{
 			Name: "one matching subscription infers package property without csv version",
-			Cache: cache.NamespacedOperatorCache{
-				Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{
-					catalog: {
-						Key: catalog,
-						Operators: []*cache.Operator{
-							{
-								Name: "a",
-								Bundle: &api.Bundle{
-									PackageName: "x",
-								},
+			Cache: cache.StaticSourceProvider{
+				catalog: &cache.Snapshot{
+					Entries: []*cache.Operator{
+						{
+							Name: "a",
+							Bundle: &api.Bundle{
+								PackageName: "x",
 							},
 						},
 					},
@@ -1773,10 +1652,8 @@ func TestInferProperties(t *testing.T) {
 			require := require.New(t)
 			logger, _ := test.NewNullLogger()
 			r := SatResolver{
-				log: logger,
-				cache: &FakeOperatorCache{
-					fakedNamespacedOperatorCache: tc.Cache,
-				},
+				log:   logger,
+				cache: cache.New(tc.Cache),
 			}
 			actual, err := r.inferProperties(tc.CSV, tc.Subscriptions)
 			require.NoError(err)
diff --git a/pkg/controller/registry/resolver/solver/bench_test.go b/pkg/controller/registry/resolver/solver/bench_test.go
index af07d55ce..72f586e1e 100644
--- a/pkg/controller/registry/resolver/solver/bench_test.go
+++ b/pkg/controller/registry/resolver/solver/bench_test.go
@@ -72,3 +72,12 @@ func BenchmarkSolve(b *testing.B) {
 		s.Solve(context.Background())
 	}
 }
+
+func BenchmarkNewInput(b *testing.B) {
+	for i := 0; i < b.N; i++ {
+		_, err := New(WithInput(BenchmarkInput))
+		if err != nil {
+			b.Fatalf("failed to initialize solver: %s", err)
+		}
+	}
+}
diff --git a/pkg/controller/registry/resolver/solver/search_test.go b/pkg/controller/registry/resolver/solver/search_test.go
index 53dff9c74..5b00bcfd2 100644
--- a/pkg/controller/registry/resolver/solver/search_test.go
+++ b/pkg/controller/registry/resolver/solver/search_test.go
@@ -1,4 +1,4 @@
-//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -o zz_search_test.go ../../../../../vendor/github.com/operator-framework/gini/inter S
+//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -o zz_search_test.go ../../../../../vendor/github.com/go-air/gini/inter S
 
 package solver
 
@@ -6,8 +6,8 @@ import (
 	"context"
 	"testing"
 
-	"github.com/operator-framework/gini/inter"
-	"github.com/operator-framework/gini/z"
+	"github.com/go-air/gini/inter"
+	"github.com/go-air/gini/z"
 	"github.com/stretchr/testify/assert"
 )
 
diff --git a/pkg/controller/registry/resolver/solver/zz_search_test.go b/pkg/controller/registry/resolver/solver/zz_search_test.go
index 5ef7eed2c..6fbdbccc4 100644
--- a/pkg/controller/registry/resolver/solver/zz_search_test.go
+++ b/pkg/controller/registry/resolver/solver/zz_search_test.go
@@ -5,8 +5,8 @@ import (
 	"sync"
 	"time"
 
-	"github.com/operator-framework/gini/inter"
-	"github.com/operator-framework/gini/z"
+	"github.com/go-air/gini/inter"
+	"github.com/go-air/gini/z"
 )
 
 type FakeS struct {
diff --git a/pkg/controller/registry/resolver/step_resolver_test.go b/pkg/controller/registry/resolver/step_resolver_test.go
index eeed54fee..77faa38b9 100644
--- a/pkg/controller/registry/resolver/step_resolver_test.go
+++ b/pkg/controller/registry/resolver/step_resolver_test.go
@@ -23,7 +23,6 @@ import (
 	"github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned/fake"
 	"github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/informers/externalversions"
 	controllerbundle "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/bundle"
-	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry"
 	resolvercache "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache"
 	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/solver"
 	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorlister"
@@ -50,7 +49,7 @@ var (
 
 func TestResolver(t *testing.T) {
 	const namespace = "catsrc-namespace"
-	catalog := registry.CatalogKey{Name: "catsrc", Namespace: namespace}
+	catalog := resolvercache.SourceKey{Name: "catsrc", Namespace: namespace}
 
 	type resolverTestOut struct {
 		steps       [][]*v1alpha1.Step
@@ -62,7 +61,7 @@ func TestResolver(t *testing.T) {
 	type resolverTest struct {
 		name             string
 		clusterState     []runtime.Object
-		bundlesByCatalog map[registry.CatalogKey][]*api.Bundle
+		bundlesByCatalog map[resolvercache.SourceKey][]*api.Bundle
 		out              resolverTestOut
 	}
 
@@ -77,7 +76,7 @@ func TestResolver(t *testing.T) {
 			clusterState: []runtime.Object{
 				newSub(namespace, "package", "", catalog),
 			},
-			bundlesByCatalog: map[registry.CatalogKey][]*api.Bundle{
+			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{
 				catalog: {
 					bundle("bundle", "package", "channel", "", nil, nil, nil, nil),
 				},
@@ -114,7 +113,7 @@ func TestResolver(t *testing.T) {
 			clusterState: []runtime.Object{
 				newSub(namespace, "a", "alpha", catalog),
 			},
-			bundlesByCatalog: map[registry.CatalogKey][]*api.Bundle{
+			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{
 				catalog: {
 					bundle("bundle", "package", "channel", "", nil, nil, nil, nil),
 				},
@@ -137,7 +136,7 @@ func TestResolver(t *testing.T) {
 			clusterState: []runtime.Object{
 				newSub(namespace, "a", "alpha", catalog),
 			},
-			bundlesByCatalog: map[registry.CatalogKey][]*api.Bundle{
+			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{
 				catalog: {
 					bundle("bundle", "a", "channel", "", nil, nil, nil, nil),
 				},
@@ -160,7 +159,7 @@ func TestResolver(t *testing.T) {
 			clusterState: []runtime.Object{
 				newSub(namespace, "a", "alpha", catalog, withStartingCSV("notfound")),
 			},
-			bundlesByCatalog: map[registry.CatalogKey][]*api.Bundle{
+			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{
 				catalog: {
 					bundle("bundle", "a", "alpha", "", nil, nil, nil, nil),
 				},
@@ -183,7 +182,7 @@ func TestResolver(t *testing.T) {
 			clusterState: []runtime.Object{
 				newSub(namespace, "a", "alpha", catalog),
 			},
-			bundlesByCatalog: map[registry.CatalogKey][]*api.Bundle{
+			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{
 				catalog: {
 					bundle("a.v1", "a", "alpha", "", nil, nil, nil, nil),
 				},
@@ -202,7 +201,7 @@ func TestResolver(t *testing.T) {
 			clusterState: []runtime.Object{
 				newSub(namespace, "a", "alpha", catalog),
 			},
-			bundlesByCatalog: map[registry.CatalogKey][]*api.Bundle{
+			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{
 				catalog: {
 					bundle("b.v1", "b", "beta", "", Provides1, nil, nil, nil),
 					bundle("a.v1", "a", "alpha", "", nil, Requires1, nil, nil),
@@ -224,7 +223,7 @@ func TestResolver(t *testing.T) {
 			clusterState: []runtime.Object{
 				newSub(namespace, "a", "alpha", catalog),
 			},
-			bundlesByCatalog: map[registry.CatalogKey][]*api.Bundle{
+			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{
 				catalog: {
 					bundle("a.v1", "a", "alpha", "", nil, Requires1, nil, nil),
 					stripManifests(withBundlePath(bundle("b.v1", "b", "beta", "", Provides1, nil, nil, nil), "quay.io/test/bundle@sha256:abcd")),
@@ -270,7 +269,7 @@ func TestResolver(t *testing.T) {
 			clusterState: []runtime.Object{
 				newSub(namespace, "a", "alpha", catalog),
 			},
-			bundlesByCatalog: map[registry.CatalogKey][]*api.Bundle{
+			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{
 				catalog: {
 					withBundleObject(bundle("b.v1", "b", "beta", "", Provides1, nil, nil, nil), u(&rbacv1.RoleBinding{TypeMeta: metav1.TypeMeta{Kind: "RoleBinding", APIVersion: "rbac.authorization.k8s.io/v1"}, ObjectMeta: metav1.ObjectMeta{Name: "test-rb"}})),
 					bundle("a.v1", "a", "alpha", "", nil, Requires1, nil, nil),
@@ -292,7 +291,7 @@ func TestResolver(t *testing.T) {
 			clusterState: []runtime.Object{
 				newSub(namespace, "a", "alpha", catalog),
 			},
-			bundlesByCatalog: map[registry.CatalogKey][]*api.Bundle{
+			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{
 				catalog: {
 					withBundleObject(bundle("b.v1", "b", "beta", "", Provides1, nil, nil, nil), u(&corev1.Service{TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: ""}, ObjectMeta: metav1.ObjectMeta{Name: "test-service"}})),
 					bundle("a.v1", "a", "alpha", "", nil, Requires1, nil, nil),
@@ -314,7 +313,7 @@ func TestResolver(t *testing.T) {
 			clusterState: []runtime.Object{
 				newSub(namespace, "a", "alpha", catalog),
 			},
-			bundlesByCatalog: map[registry.CatalogKey][]*api.Bundle{
+			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{
 				catalog: {
 					bundle("a.v1", "a", "alpha", "", nil, Requires1, nil, nil),
 				},
@@ -348,7 +347,7 @@ func TestResolver(t *testing.T) {
 				existingSub(namespace, "a.v1", "a", "alpha", catalog),
 				existingOperator(namespace, "a.v1", "a", "alpha", "", Provides1, nil, nil, nil),
 			},
-			bundlesByCatalog: map[registry.CatalogKey][]*api.Bundle{
+			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{
 				catalog: {
 					bundle("a.v1", "a", "alpha", "", Provides1, nil, nil, nil),
 				},
@@ -362,7 +361,7 @@ func TestResolver(t *testing.T) {
 				existingSub(namespace, "b.v1", "b", "alpha", catalog),
 				existingOperator(namespace, "a.v1", "a", "alpha", "", Provides1, nil, nil, nil),
 			},
-			bundlesByCatalog: map[registry.CatalogKey][]*api.Bundle{
+			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{
 				catalog: {
 					bundle("a.v1", "a", "alpha", "", Provides1, nil, nil, nil),
 					bundle("b.v1", "b", "alpha", "", Provides1, nil, nil, nil),
@@ -380,7 +379,7 @@ func TestResolver(t *testing.T) {
 				newSub(namespace, "a", "alpha", catalog),
 				newSub(namespace, "a", "beta", catalog),
 			},
-			bundlesByCatalog: map[registry.CatalogKey][]*api.Bundle{
+			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{
 				catalog: {
 					bundle("a.v1", "a", "alpha", "", Provides1, nil, nil, nil),
 					bundle("a.v2", "a", "beta", "", Provides1, nil, nil, nil),
@@ -406,7 +405,7 @@ func TestResolver(t *testing.T) {
 					return
 				}(),
 			},
-			bundlesByCatalog: map[registry.CatalogKey][]*api.Bundle{
+			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{
 				catalog: {
 					bundle("a.v1", "a", "alpha", "", Provides1, nil, nil, nil),
 				},
@@ -439,7 +438,7 @@ func TestResolver(t *testing.T) {
 				existingSub(namespace, "a.v1", "a", "alpha", catalog),
 				existingOperator(namespace, "a.v1", "a", "alpha", "", Provides1, nil, nil, nil),
 			},
-			bundlesByCatalog: map[registry.CatalogKey][]*api.Bundle{
+			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{
 				catalog: {
 					bundle("a.v2", "a", "alpha", "a.v1", Provides1, nil, nil, nil),
 					bundle("a.v1", "a", "alpha", "", Provides1, nil, nil, nil),
@@ -460,7 +459,7 @@ func TestResolver(t *testing.T) {
 				existingSub(namespace, "a.v1", "a", "alpha", catalog),
 				existingOperator(namespace, "a.v1", "a", "alpha", "", Provides1, nil, nil, nil),
 			},
-			bundlesByCatalog: map[registry.CatalogKey][]*api.Bundle{catalog: {
+			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{catalog: {
 				stripManifests(withBundlePath(bundle("a.v2", "a", "alpha", "a.v1", Provides1, nil, nil, nil), "quay.io/test/bundle@sha256:abcd"))},
 			},
 			out: resolverTestOut{
@@ -501,7 +500,7 @@ func TestResolver(t *testing.T) {
 			clusterState: []runtime.Object{
 				existingSub(namespace, "a.v1", "a", "alpha", catalog),
 			},
-			bundlesByCatalog: map[registry.CatalogKey][]*api.Bundle{
+			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{
 				catalog: {
 					bundle("a.v1", "a", "alpha", "", Provides1, nil, nil, nil),
 				},
@@ -520,7 +519,7 @@ func TestResolver(t *testing.T) {
 				existingSub(namespace, "a.v1", "a", "alpha", catalog),
 				existingOperator(namespace, "a.v1", "a", "alpha", "", Provides1, nil, nil, nil),
 			},
-			bundlesByCatalog: map[registry.CatalogKey][]*api.Bundle{
+			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{
 				catalog: {
 					bundle("a.v1", "a", "alpha", "", nil, nil, nil, nil),
 					bundle("a.v2", "a", "alpha", "a.v1", nil, Requires1, nil, nil),
@@ -544,7 +543,7 @@ func TestResolver(t *testing.T) {
 				existingSub(namespace, "a.v1", "a", "alpha", catalog),
 				existingOperator(namespace, "a.v1", "a", "alpha", "", nil, nil, Provides1, nil),
 			},
-			bundlesByCatalog: map[registry.CatalogKey][]*api.Bundle{
+			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{
 				catalog: {
 					bundle("a.v1", "a", "alpha", "", nil, nil, nil, nil),
 					bundle("a.v2", "a", "alpha", "a.v1", nil, nil, nil, Requires1),
@@ -569,7 +568,7 @@ func TestResolver(t *testing.T) {
 				existingOperator(namespace, "a.v1", "a", "alpha", "", Provides1, nil, nil, nil),
 				newSub(namespace, "b", "beta", catalog),
 			},
-			bundlesByCatalog: map[registry.CatalogKey][]*api.Bundle{
+			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{
 				catalog: {
 					bundle("a.v1", "a", "alpha", "", nil, nil, nil, nil),
 					bundle("a.v2", "a", "alpha", "a.v1", nil, nil, nil, nil),
@@ -593,7 +592,7 @@ func TestResolver(t *testing.T) {
 				existingSub(namespace, "a.v1", "a", "alpha", catalog),
 				newSub(namespace, "b", "beta", catalog),
 			},
-			bundlesByCatalog: map[registry.CatalogKey][]*api.Bundle{
+			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{
 				catalog: {
 					bundle("a.v1", "a", "alpha", "", Provides1, nil, nil, nil),
 					bundle("b.v1", "b", "beta", "", nil, nil, nil, nil),
@@ -615,7 +614,7 @@ func TestResolver(t *testing.T) {
 				existingSub(namespace, "a.v1", "a", "alpha", catalog),
 				newSub(namespace, "b", "beta", catalog),
 			},
-			bundlesByCatalog: map[registry.CatalogKey][]*api.Bundle{
+			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{
 				catalog: {
 					bundle("a.v1", "a", "alpha", "", nil, nil, Provides1, nil),
 					bundle("b.v1", "b", "beta", "", nil, nil, nil, nil),
@@ -640,7 +639,7 @@ func TestResolver(t *testing.T) {
 				existingSub(namespace, "b.v1", "b", "alpha", catalog),
 				existingOperator(namespace, "b.v1", "b", "alpha", "", Provides2, Requires1, nil, nil),
 			},
-			bundlesByCatalog: map[registry.CatalogKey][]*api.Bundle{
+			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{
 				catalog: {
 					bundle("a.v2", "a", "alpha", "a.v1", Provides3, Requires4, nil, nil),
 					bundle("b.v2", "b", "alpha", "b.v1", Provides4, Requires3, nil, nil),
@@ -666,7 +665,7 @@ func TestResolver(t *testing.T) {
 				existingSub(namespace, "b.v1", "b", "alpha", catalog),
 				existingOperator(namespace, "b.v1", "b", "alpha", "", nil, Requires1, nil, nil),
 			},
-			bundlesByCatalog: map[registry.CatalogKey][]*api.Bundle{
+			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{
 				catalog: {
 					bundle("a.v2", "a", "alpha", "a.v1", nil, nil, nil, nil),
 					bundle("b.v2", "b", "alpha", "b.v1", Provides1, nil, nil, nil),
@@ -688,7 +687,7 @@ func TestResolver(t *testing.T) {
 			clusterState: []runtime.Object{
 				newSub(namespace, "b", "alpha", catalog),
 			},
-			bundlesByCatalog: map[registry.CatalogKey][]*api.Bundle{
+			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{
 				catalog: {
 					bundle("a.v1", "a", "alpha", "", Provides1, nil, nil, nil),
 					bundle("a.v2", "a", "alpha", "a.v1", nil, nil, nil, nil),
@@ -712,7 +711,7 @@ func TestResolver(t *testing.T) {
 				existingSub(namespace, "a.v1", "a", "alpha", catalog),
 				existingOperator(namespace, "a.v1", "a", "alpha", "", Provides1, nil, nil, nil),
 			},
-			bundlesByCatalog: map[registry.CatalogKey][]*api.Bundle{catalog: {
+			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{catalog: {
 				bundle("a.v3", "a", "alpha", "a.v2", nil, nil, nil, nil, withVersion("1.0.0"), withSkipRange("< 1.0.0")),
 			}},
 			out: resolverTestOut{
@@ -732,9 +731,9 @@ func TestResolver(t *testing.T) {
 				existingOperator(namespace, "a.v1", "a", "alpha", "", nil, Requires1, nil, nil),
 				existingOperator(namespace, "b.v1", "b", "beta", "", Provides1, nil, nil, nil),
 			},
-			bundlesByCatalog: map[registry.CatalogKey][]*api.Bundle{
+			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{
 				catalog: {
-					bundle("a.v1", "a", "alpha", "", nil, nil, nil, nil),
+					bundle("a.v1", "a", "alpha", "", nil, Requires1, nil, nil),
 					bundle("a.v2", "a", "alpha", "a.v1", nil, Requires1, nil, nil),
 					bundle("b.v1", "b", "beta", "", Provides1, nil, nil, nil),
 					bundle("b.v2", "b", "beta", "b.v1", Provides1, nil, nil, nil),
@@ -757,7 +756,7 @@ func TestResolver(t *testing.T) {
 				existingSub(namespace, "a.v1", "a", "alpha", catalog),
 				existingOperator(namespace, "a.v1", "a", "alpha", "", Provides1, nil, nil, nil),
 			},
-			bundlesByCatalog: map[registry.CatalogKey][]*api.Bundle{catalog: {
+			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{catalog: {
 				bundle("a.v2", "a", "alpha", "", nil, nil, nil, nil, withVersion("1.0.0"), withSkipRange("< 1.0.0")),
 				bundle("a.v3", "a", "alpha", "a.v2", nil, nil, nil, nil, withVersion("1.0.0"), withSkipRange("< 1.0.0")),
 				bundle("a.v4", "a", "alpha", "a.v3", nil, nil, nil, nil, withVersion("1.0.0"), withSkipRange("< 1.0.0 !0.0.0")),
@@ -776,7 +775,7 @@ func TestResolver(t *testing.T) {
 			clusterState: []runtime.Object{
 				newSub(namespace, "a", "alpha", catalog, withStartingCSV("a.v2")),
 			},
-			bundlesByCatalog: map[registry.CatalogKey][]*api.Bundle{catalog: {
+			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{catalog: {
 				bundle("a.v1", "a", "alpha", "", nil, nil, nil, nil),
 				bundle("a.v2", "a", "alpha", "a.v1", nil, nil, nil, nil),
 				bundle("a.v3", "a", "alpha", "a.v2", nil, nil, nil, nil, withVersion("1.0.0"), withSkipRange("< 1.0.0")),
@@ -796,7 +795,7 @@ func TestResolver(t *testing.T) {
 				existingSub(namespace, "a.v1", "a", "alpha", catalog),
 				existingOperator(namespace, "a.v1", "a", "alpha", "", Provides1, nil, nil, nil),
 			},
-			bundlesByCatalog: map[registry.CatalogKey][]*api.Bundle{catalog: {
+			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{catalog: {
 				bundle("a.v2", "a", "alpha", "", nil, nil, nil, nil, withVersion("1.0.0"), withSkips([]string{"a.v1"})),
 				bundle("a.v3", "a", "alpha", "a.v2", nil, nil, nil, nil, withVersion("1.0.0"), withSkips([]string{"a.v1"})),
 			}},
@@ -826,26 +825,21 @@ func TestResolver(t *testing.T) {
 			lister.OperatorsV1alpha1().RegisterClusterServiceVersionLister(namespace, informerFactory.Operators().V1alpha1().ClusterServiceVersions().Lister())
 			kClientFake := k8sfake.NewSimpleClientset()
 
-			stubSnapshot := &resolvercache.CatalogSnapshot{}
-			for _, bundles := range tt.bundlesByCatalog {
+			ssp := make(resolvercache.StaticSourceProvider)
+			for catalog, bundles := range tt.bundlesByCatalog {
+				snapshot := &resolvercache.Snapshot{}
 				for _, bundle := range bundles {
 					op, err := resolvercache.NewOperatorFromBundle(bundle, "", catalog, "")
 					if err != nil {
 						t.Fatalf("unexpected error: %v", err)
 					}
-					stubSnapshot.Operators = append(stubSnapshot.Operators, op)
+					snapshot.Entries = append(snapshot.Entries, op)
 				}
-			}
-			stubCache := &stubOperatorCacheProvider{
-				noc: &resolvercache.NamespacedOperatorCache{
-					Snapshots: map[registry.CatalogKey]*resolvercache.CatalogSnapshot{
-						catalog: stubSnapshot,
-					},
-				},
+				ssp[catalog] = snapshot
 			}
 			log := logrus.New()
 			satresolver := &SatResolver{
-				cache: stubCache,
+				cache: resolvercache.New(ssp),
 				log:   log,
 			}
 			resolver := NewOperatorStepResolver(lister, clientFake, kClientFake, "", nil, log)
@@ -886,13 +880,13 @@ func (stub *stubOperatorCacheProvider) Namespaced(namespaces ...string) resolver
 	return stub.noc
 }
 
-func (stub *stubOperatorCacheProvider) Expire(key registry.CatalogKey) {
+func (stub *stubOperatorCacheProvider) Expire(key resolvercache.SourceKey) {
 	return
 }
 
 func TestNamespaceResolverRBAC(t *testing.T) {
 	namespace := "catsrc-namespace"
-	catalog := registry.CatalogKey{"catsrc", namespace}
+	catalog := resolvercache.SourceKey{"catsrc", namespace}
 
 	simplePermissions := []v1alpha1.StrategyDeploymentPermissions{
 		{
@@ -978,23 +972,18 @@ func TestNamespaceResolverRBAC(t *testing.T) {
 			lister.OperatorsV1alpha1().RegisterSubscriptionLister(namespace, informerFactory.Operators().V1alpha1().Subscriptions().Lister())
 			lister.OperatorsV1alpha1().RegisterClusterServiceVersionLister(namespace, informerFactory.Operators().V1alpha1().ClusterServiceVersions().Lister())
 
-			stubSnapshot := &resolvercache.CatalogSnapshot{}
+			stubSnapshot := &resolvercache.Snapshot{}
 			for _, bundle := range tt.bundlesInCatalog {
 				op, err := resolvercache.NewOperatorFromBundle(bundle, "", catalog, "")
 				if err != nil {
 					t.Fatalf("unexpected error: %v", err)
 				}
-				stubSnapshot.Operators = append(stubSnapshot.Operators, op)
-			}
-			stubCache := &stubOperatorCacheProvider{
-				noc: &resolvercache.NamespacedOperatorCache{
-					Snapshots: map[registry.CatalogKey]*resolvercache.CatalogSnapshot{
-						catalog: stubSnapshot,
-					},
-				},
+				stubSnapshot.Entries = append(stubSnapshot.Entries, op)
 			}
 			satresolver := &SatResolver{
-				cache: stubCache,
+				cache: resolvercache.New(resolvercache.StaticSourceProvider{
+					catalog: stubSnapshot,
+				}),
 			}
 			resolver := NewOperatorStepResolver(lister, clientFake, kClientFake, "", nil, logrus.New())
 			resolver.satResolver = satresolver
@@ -1038,7 +1027,7 @@ func withStartingCSV(name string) subOption {
 	}
 }
 
-func newSub(namespace, pkg, channel string, catalog registry.CatalogKey, option ...subOption) *v1alpha1.Subscription {
+func newSub(namespace, pkg, channel string, catalog resolvercache.SourceKey, option ...subOption) *v1alpha1.Subscription {
 	s := &v1alpha1.Subscription{
 		ObjectMeta: metav1.ObjectMeta{
 			Name:      pkg + "-" + channel,
@@ -1057,7 +1046,7 @@ func newSub(namespace, pkg, channel string, catalog registry.CatalogKey, option
 	return s
 }
 
-func updatedSub(namespace, currentOperatorName, installedOperatorName, pkg, channel string, catalog registry.CatalogKey, option ...subOption) *v1alpha1.Subscription {
+func updatedSub(namespace, currentOperatorName, installedOperatorName, pkg, channel string, catalog resolvercache.SourceKey, option ...subOption) *v1alpha1.Subscription {
 	s := &v1alpha1.Subscription{
 		ObjectMeta: metav1.ObjectMeta{
 			Name:      pkg + "-" + channel,
@@ -1080,7 +1069,7 @@ func updatedSub(namespace, currentOperatorName, installedOperatorName, pkg, chan
 	return s
 }
 
-func existingSub(namespace, operatorName, pkg, channel string, catalog registry.CatalogKey) *v1alpha1.Subscription {
+func existingSub(namespace, operatorName, pkg, channel string, catalog resolvercache.SourceKey) *v1alpha1.Subscription {
 	return &v1alpha1.Subscription{
 		ObjectMeta: metav1.ObjectMeta{
 			Name:      pkg + "-" + channel,
@@ -1109,7 +1098,7 @@ func existingOperator(namespace, operatorName, pkg, channel, replaces string, pr
 	return csv
 }
 
-func bundleSteps(bundle *api.Bundle, ns, replaces string, catalog registry.CatalogKey) []*v1alpha1.Step {
+func bundleSteps(bundle *api.Bundle, ns, replaces string, catalog resolvercache.SourceKey) []*v1alpha1.Step {
 	if replaces == "" {
 		csv, _ := V1alpha1CSVFromBundle(bundle)
 		replaces = csv.Spec.Replaces
@@ -1142,7 +1131,7 @@ func withoutResourceKind(kind string, steps []*v1alpha1.Step) []*v1alpha1.Step {
 	return filtered
 }
 
-func subSteps(namespace, operatorName, pkgName, channelName string, catalog registry.CatalogKey) []*v1alpha1.Step {
+func subSteps(namespace, operatorName, pkgName, channelName string, catalog resolvercache.SourceKey) []*v1alpha1.Step {
 	sub := &v1alpha1.Subscription{
 		ObjectMeta: metav1.ObjectMeta{
 			Name:      strings.Join([]string{pkgName, channelName, catalog.Name, catalog.Namespace}, "-"),
diff --git a/test/e2e/metrics_e2e_test.go b/test/e2e/metrics_e2e_test.go
index 56cef82d3..2769091e3 100644
--- a/test/e2e/metrics_e2e_test.go
+++ b/test/e2e/metrics_e2e_test.go
@@ -16,6 +16,7 @@ import (
 	. "github.com/onsi/gomega"
 	io_prometheus_client "github.com/prometheus/client_model/go"
 	"github.com/prometheus/common/expfmt"
+	appsv1 "k8s.io/api/apps/v1"
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -116,6 +117,44 @@ var _ = Describe("Metrics are generated for OLM managed resources", func() {
 				})
 			})
 		})
+
+		When("a CSV is created", func() {
+			var (
+				cleanupCSV cleanupFunc
+				csv        v1alpha1.ClusterServiceVersion
+			)
+			BeforeEach(func() {
+				packageName := genName("csv-test-")
+				packageStable := fmt.Sprintf("%s-stable", packageName)
+				csv = newCSV(packageStable, testNamespace, "", semver.MustParse("0.1.0"), nil, nil, nil)
+
+				var err error
+				_, err = createCSV(c, crc, csv, testNamespace, false, false)
+				Expect(err).ToNot(HaveOccurred())
+				_, err = fetchCSV(crc, csv.Name, testNamespace, csvSucceededChecker)
+				Expect(err).ToNot(HaveOccurred())
+			})
+			AfterEach(func() {
+				if cleanupCSV != nil {
+					cleanupCSV()
+				}
+			})
+			It("emits a CSV metrics", func() {
+				Expect(getMetricsFromPod(c, getPodWithLabel(c, "app=olm-operator"))).To(
+					ContainElement(LikeMetric(WithFamily("csv_succeeded"), WithName(csv.Name), WithValue(1))),
+				)
+			})
+			When("the OLM pod restarts", func() {
+				BeforeEach(func() {
+					restartDeploymentWithLabel(c, "app=olm-operator")
+				})
+				It("CSV metric is preserved", func() {
+					Expect(getMetricsFromPod(c, getPodWithLabel(c, "app=olm-operator"))).To(
+						ContainElement(LikeMetric(WithFamily("csv_succeeded"), WithName(csv.Name), WithValue(1))),
+					)
+				})
+			})
+		})
 	})
 
 	Context("Metrics emitted by objects during operator installation", func() {
@@ -396,6 +435,51 @@ func getPodWithLabel(client operatorclient.ClientInterface, label string) *corev
 	return &podList.Items[0]
 }
 
+func getDeploymentWithLabel(client operatorclient.ClientInterface, label string) *appsv1.Deployment {
+	listOptions := metav1.ListOptions{LabelSelector: label}
+	var deploymentList *appsv1.DeploymentList
+	EventuallyWithOffset(1, func() (numDeps int, err error) {
+		deploymentList, err = client.KubernetesInterface().AppsV1().Deployments(operatorNamespace).List(context.TODO(), listOptions)
+		if deploymentList != nil {
+			numDeps = len(deploymentList.Items)
+		}
+
+		return
+	}).Should(Equal(1), "expected exactly one Deployment")
+
+	return &deploymentList.Items[0]
+}
+
+func restartDeploymentWithLabel(client operatorclient.ClientInterface, l string) {
+	d := getDeploymentWithLabel(client, l)
+	z := int32(0)
+	oldZ := *d.Spec.Replicas
+	d.Spec.Replicas = &z
+	_, err := client.KubernetesInterface().AppsV1().Deployments(operatorNamespace).Update(context.TODO(), d, metav1.UpdateOptions{})
+	Expect(err).ToNot(HaveOccurred())
+
+	EventuallyWithOffset(1, func() (replicas int32, err error) {
+		deployment, err := client.KubernetesInterface().AppsV1().Deployments(operatorNamespace).Get(context.TODO(), d.Name, metav1.GetOptions{})
+		if deployment != nil {
+			replicas = deployment.Status.Replicas
+		}
+		return
+	}).Should(Equal(int32(0)), "expected exactly 0 Deployments")
+
+	updated := getDeploymentWithLabel(client, l)
+	updated.Spec.Replicas = &oldZ
+	_, err = client.KubernetesInterface().AppsV1().Deployments(operatorNamespace).Update(context.TODO(), updated, metav1.UpdateOptions{})
+	Expect(err).ToNot(HaveOccurred())
+
+	EventuallyWithOffset(1, func() (replicas int32, err error) {
+		deployment, err := client.KubernetesInterface().AppsV1().Deployments(operatorNamespace).Get(context.TODO(), d.Name, metav1.GetOptions{})
+		if deployment != nil {
+			replicas = deployment.Status.Replicas
+		}
+		return
+	}).Should(Equal(oldZ), "expected exactly 1 Deployment")
+}
+
 func extractMetricPortFromPod(pod *corev1.Pod) string {
 	for _, container := range pod.Spec.Containers {
 		for _, port := range container.Ports {
diff --git a/test/e2e/subscription_e2e_test.go b/test/e2e/subscription_e2e_test.go
index cb2183b0a..a8ab2b676 100644
--- a/test/e2e/subscription_e2e_test.go
+++ b/test/e2e/subscription_e2e_test.go
@@ -2112,8 +2112,11 @@ var _ = Describe("Subscription", func() {
 			teardown   func()
 			cleanup    func()
 			packages   []registry.PackageManifest
-			subName    = genName("test-subscription")
-			catSrcName = genName("test-catalog")
+			crd        = newCRD(genName("foo-"))
+			csvA       operatorsv1alpha1.ClusterServiceVersion
+			csvB       operatorsv1alpha1.ClusterServiceVersion
+			subName    = genName("test-subscription-")
+			catSrcName = genName("test-catalog-")
 		)
 
 		BeforeEach(func() {
@@ -2129,10 +2132,9 @@ var _ = Describe("Subscription", func() {
 					DefaultChannelName: "alpha",
 				},
 			}
-			crd := newCRD(genName("foo"))
-			csv := newCSV("csvA", testNamespace, "", semver.MustParse("1.0.0"), nil, []apiextensions.CustomResourceDefinition{crd}, nil)
+			csvA = newCSV("csvA", testNamespace, "", semver.MustParse("1.0.0"), nil, []apiextensions.CustomResourceDefinition{crd}, nil)
 
-			_, teardown = createInternalCatalogSource(c, ctx.Ctx().OperatorClient(), catSrcName, testNamespace, packages, nil, []operatorsv1alpha1.ClusterServiceVersion{csv})
+			_, teardown = createInternalCatalogSource(c, ctx.Ctx().OperatorClient(), catSrcName, testNamespace, packages, nil, []operatorsv1alpha1.ClusterServiceVersion{csvA})
 
 			// Ensure that the catalog source is resolved before we create a subscription.
 			_, err := fetchCatalogSourceOnStatus(crc, catSrcName, testNamespace, catalogSourceRegistryPodSynced)
@@ -2156,6 +2158,31 @@ var _ = Describe("Subscription", func() {
 			}).Should(Equal(corev1.ConditionTrue))
 		})
 
+		When("the required API is made available", func() {
+			BeforeEach(func() {
+				newPkg := registry.PackageManifest{
+					PackageName: "PackageB",
+					Channels: []registry.PackageChannel{
+						{Name: "alpha", CurrentCSVName: "csvB"},
+					},
+					DefaultChannelName: "alpha",
+				}
+				packages = append(packages, newPkg)
+
+				csvB = newCSV("csvB", testNamespace, "", semver.MustParse("1.0.0"), []apiextensions.CustomResourceDefinition{crd}, nil, nil)
+
+				updateInternalCatalog(GinkgoT(), c, crc, catSrcName, testNamespace, []apiextensions.CustomResourceDefinition{crd}, []operatorsv1alpha1.ClusterServiceVersion{csvA, csvB}, packages)
+			})
+			It("the ResolutionFailed condition previously set in it's status that indicated the resolution error is cleared off", func() {
+				Eventually(func() (corev1.ConditionStatus, error) {
+					sub, err := crc.OperatorsV1alpha1().Subscriptions(testNamespace).Get(context.Background(), subName, metav1.GetOptions{})
+					if err != nil {
+						return corev1.ConditionUnknown, err
+					}
+					return sub.Status.GetCondition(operatorsv1alpha1.SubscriptionResolutionFailed).Status, nil
+				}).Should(Equal(corev1.ConditionFalse))
+			})
+		})
 	})
 
 	When("an unannotated ClusterServiceVersion exists with an associated Subscription", func() {