Performance and scale.

Defer edge creation.

Don't create edges until the count is known to avoid repeated allocate+
copy operatios.

Limit resolutions.

Allow only a single resolution condition set per target, and overwrite
intermediate results. Reduces memory and obviates allocations.

Propagate fewer conditions.

Instead of propagating notice conditions to parents in graph during
initial resolve, leave them on leaf node, and attach to ancestors in
the final walk. Reduces copies.

Parallelize resolutions.

Use goroutines, mutexes, and waitgroups to resolve branches of the
graph in parallel. Makes better use of available cores.

Don't accumulate resolutions inside non-containers.

During the final resolution walk, only attach actions to ancestors from
the root down until the 1st non-aggregate. Prevents an explosion of
copies in the lower levels of the graph.

Drop origin for scale.

Tracking the origin of every potential origin for every restricted
condition does not scale. By dropping origin, propagating from top
to bottom can prune many redundant paths avoiding an exponential
explosion.

Conditions as bitmask.

Use bit masks for license conditions and condition sets. Reduces maps
and allocations.

Bug: 68860345
Bug: 151177513
Bug: 151953481

Test: m all
Test: m systemlicense
Test: m listshare; out/soong/host/linux-x86/bin/listshare ...
Test: m checkshare; out/soong/host/linux-x86/bin/checkshare ...
Test: m dumpgraph; out/soong/host/linux-x86/dumpgraph ...
Test: m dumpresolutions; out/soong/host/linux-x86/dumpresolutions ...

where ... is the path to the .meta_lic file for the system image. In my
case if

$ export PRODUCT=$(realpath $ANDROID_PRODUCT_OUT --relative-to=$PWD)

... can be expressed as:

${PRODUCT}/gen/META/lic_intermediates/${PRODUCT}/system.img.meta_lic

Change-Id: Ia2ec1b818de6122c239fbd0824754f1d65daffd3
diff --git a/tools/compliance/policy/policy.go b/tools/compliance/policy/policy.go
index d3e412b..581912a 100644
--- a/tools/compliance/policy/policy.go
+++ b/tools/compliance/policy/policy.go
@@ -30,31 +30,33 @@
 	}
 
 	// ImpliesUnencumbered lists the condition names representing an author attempt to disclaim copyright.
-	ImpliesUnencumbered = ConditionNames{"unencumbered"}
+	ImpliesUnencumbered = LicenseConditionSet(UnencumberedCondition)
 
 	// ImpliesPermissive lists the condition names representing copyrighted but "licensed without policy requirements".
-	ImpliesPermissive = ConditionNames{"permissive"}
+	ImpliesPermissive = LicenseConditionSet(PermissiveCondition)
 
 	// ImpliesNotice lists the condition names implying a notice or attribution policy.
-	ImpliesNotice = ConditionNames{"unencumbered", "permissive", "notice", "reciprocal", "restricted", "proprietary", "by_exception_only"}
+	ImpliesNotice = LicenseConditionSet(UnencumberedCondition | PermissiveCondition | NoticeCondition | ReciprocalCondition |
+			RestrictedCondition | RestrictedClasspathExceptionCondition | WeaklyRestrictedCondition |
+			ProprietaryCondition | ByExceptionOnlyCondition)
 
 	// ImpliesReciprocal lists the condition names implying a local source-sharing policy.
-	ImpliesReciprocal = ConditionNames{"reciprocal"}
+	ImpliesReciprocal = LicenseConditionSet(ReciprocalCondition)
 
 	// Restricted lists the condition names implying an infectious source-sharing policy.
-	ImpliesRestricted = ConditionNames{"restricted"}
+	ImpliesRestricted = LicenseConditionSet(RestrictedCondition | RestrictedClasspathExceptionCondition | WeaklyRestrictedCondition)
 
 	// ImpliesProprietary lists the condition names implying a confidentiality policy.
-	ImpliesProprietary = ConditionNames{"proprietary"}
+	ImpliesProprietary = LicenseConditionSet(ProprietaryCondition)
 
 	// ImpliesByExceptionOnly lists the condition names implying a policy for "license review and approval before use".
-	ImpliesByExceptionOnly = ConditionNames{"proprietary", "by_exception_only"}
+	ImpliesByExceptionOnly = LicenseConditionSet(ProprietaryCondition | ByExceptionOnlyCondition)
 
 	// ImpliesPrivate lists the condition names implying a source-code privacy policy.
-	ImpliesPrivate = ConditionNames{"proprietary"}
+	ImpliesPrivate = LicenseConditionSet(ProprietaryCondition)
 
 	// ImpliesShared lists the condition names implying a source-code sharing policy.
-	ImpliesShared = ConditionNames{"reciprocal", "restricted"}
+	ImpliesShared = LicenseConditionSet(ReciprocalCondition | RestrictedCondition | RestrictedClasspathExceptionCondition | WeaklyRestrictedCondition)
 )
 
 var (
@@ -64,100 +66,117 @@
 	ccBySa       = regexp.MustCompile(`^SPDX-license-identifier-CC-BY.*-SA.*`)
 )
 
-// Resolution happens in two passes:
+
+// LicenseConditionSetFromNames returns a set containing the recognized `names` and
+// silently ignoring or discarding the unrecognized `names`.
+func LicenseConditionSetFromNames(tn *TargetNode, names ...string) LicenseConditionSet {
+	cs := NewLicenseConditionSet()
+	for _, name := range names {
+		if name == "restricted" {
+			if 0 == len(tn.LicenseKinds()) {
+				cs = cs.Plus(RestrictedCondition)
+				continue
+			}
+			hasLgpl := false
+			hasClasspath := false
+			hasGeneric := false
+			for _, kind := range tn.LicenseKinds() {
+				if strings.HasSuffix(kind, "-with-classpath-exception") {
+					cs = cs.Plus(RestrictedClasspathExceptionCondition)
+					hasClasspath = true
+				} else if anyLgpl.MatchString(kind) {
+					cs = cs.Plus(WeaklyRestrictedCondition)
+					hasLgpl = true
+				} else if versionedGpl.MatchString(kind) {
+					cs = cs.Plus(RestrictedCondition)
+				} else if genericGpl.MatchString(kind) {
+					hasGeneric = true
+				} else if kind == "legacy_restricted" || ccBySa.MatchString(kind) {
+					cs = cs.Plus(RestrictedCondition)
+				} else {
+					cs = cs.Plus(RestrictedCondition)
+				}
+			}
+			if hasGeneric && !hasLgpl && !hasClasspath {
+				cs = cs.Plus(RestrictedCondition)
+			}
+			continue
+		}
+		if lc, ok := RecognizedConditionNames[name]; ok {
+			cs |= LicenseConditionSet(lc)
+		}
+	}
+	return cs
+}
+
+
+// Resolution happens in three phases:
 //
-// 1. A bottom-up traversal propagates license conditions up to targets from
-// dendencies as needed.
+// 1. A bottom-up traversal propagates (restricted) license conditions up to
+// targets from dendencies as needed.
 //
-// 2. For each condition of interest, a top-down traversal adjusts the attached
-// conditions pushing restricted down from targets into linked dependencies.
+// 2. For each condition of interest, a top-down traversal propagates
+// (restricted) conditions down from targets into linked dependencies.
 //
-// The behavior of the 2 passes gets controlled by the 2 functions below.
+// 3. Finally, a walk of the shipped target nodes attaches resolutions to the
+// ancestor nodes from the root down to and including the first non-container.
 //
-// The first function controls what happens during the bottom-up traversal. In
-// general conditions flow up through static links but not other dependencies;
-// except, restricted sometimes flows up through dynamic links.
+// e.g. If a disk image contains a binary bin1 that links a library liba, the
+// notice requirement for liba gets attached to the disk image and to bin1.
+// Because liba doesn't actually get shipped as a separate artifact, but only
+// as bits in bin1, it has no actions 'attached' to it. The actions attached
+// to the image and to bin1 'act on' liba by providing notice.
 //
-// In general, too, the originating target gets acted on to resolve the
-// condition (e.g. providing notice), but again restricted is special in that
-// it requires acting on (i.e. sharing source of) both the originating module
-// and the target using the module.
+// The behavior of the 3 phases gets controlled by the 3 functions below.
 //
-// The latter function controls what happens during the top-down traversal. In
-// general, only restricted conditions flow down at all, and only through
-// static links.
+// The first function controls what happens during the bottom-up propagation.
+// Restricted conditions propagate up all non-toolchain dependencies; except,
+// some do not propagate up dynamic links, which may depend on whether the
+// modules are independent.
+//
+// The second function controls what happens during the top-down propagation.
+// Restricted conditions propagate down as above with the added caveat that
+// inherited restricted conditions do not propagate from pure aggregates to
+// their dependencies.
+//
+// The final function controls which conditions apply/get attached to ancestors
+// depending on the types of dependencies involved. All conditions apply across
+// normal derivation dependencies. No conditions apply across toolchain
+// dependencies. Some restricted conditions apply across dynamic link
+// dependencies.
 //
 // Not all restricted licenses are create equal. Some have special rules or
 // exceptions. e.g. LGPL or "with classpath excption".
 
-// depActionsApplicableToTarget returns the actions which propagate up an
+
+// depConditionsPropagatingToTarget returns the conditions which propagate up an
 // edge from dependency to target.
 //
-// This function sets the policy for the bottom-up traversal and how conditions
+// This function sets the policy for the bottom-up propagation and how conditions
 // flow up the graph from dependencies to targets.
 //
 // If a pure aggregation is built into a derivative work that is not a pure
 // aggregation, per policy it ceases to be a pure aggregation in the context of
 // that derivative work. The `treatAsAggregate` parameter will be false for
 // non-aggregates and for aggregates in non-aggregate contexts.
-func depActionsApplicableToTarget(e TargetEdge, depActions actionSet, treatAsAggregate bool) actionSet {
-	result := make(actionSet)
+func depConditionsPropagatingToTarget(lg *LicenseGraph, e *TargetEdge, depConditions LicenseConditionSet, treatAsAggregate bool) LicenseConditionSet {
+	result := LicenseConditionSet(0x0000)
 	if edgeIsDerivation(e) {
-		result.addSet(depActions)
-		for _, cs := range depActions.byName(ImpliesRestricted) {
-			result.add(e.Target(), cs)
-		}
+		result |= depConditions & ImpliesRestricted
 		return result
 	}
 	if !edgeIsDynamicLink(e) {
 		return result
 	}
 
-	restricted := depActions.byName(ImpliesRestricted)
-	for actsOn, cs := range restricted {
-		for _, lc := range cs.AsList() {
-			hasGpl := false
-			hasLgpl := false
-			hasClasspath := false
-			hasGeneric := false
-			hasOther := false
-			for _, kind := range lc.origin.LicenseKinds() {
-				if strings.HasSuffix(kind, "-with-classpath-exception") {
-					hasClasspath = true
-				} else if anyLgpl.MatchString(kind) {
-					hasLgpl = true
-				} else if versionedGpl.MatchString(kind) {
-					hasGpl = true
-				} else if genericGpl.MatchString(kind) {
-					hasGeneric = true
-				} else if kind == "legacy_restricted" || ccBySa.MatchString(kind) {
-					hasOther = true
-				}
-			}
-			if hasOther || hasGpl {
-				result.addCondition(actsOn, lc)
-				result.addCondition(e.Target(), lc)
-				continue
-			}
-			if hasClasspath && !edgeNodesAreIndependentModules(e) {
-				result.addCondition(actsOn, lc)
-				result.addCondition(e.Target(), lc)
-				continue
-			}
-			if hasLgpl || hasClasspath {
-				continue
-			}
-			if !hasGeneric {
-				continue
-			}
-			result.addCondition(actsOn, lc)
-			result.addCondition(e.Target(), lc)
-		}
+	result |= depConditions & LicenseConditionSet(RestrictedCondition)
+	if 0 != (depConditions & LicenseConditionSet(RestrictedClasspathExceptionCondition)) && !edgeNodesAreIndependentModules(e) {
+		result |= LicenseConditionSet(RestrictedClasspathExceptionCondition)
 	}
 	return result
 }
 
-// targetConditionsApplicableToDep returns the conditions which propagate down
+// targetConditionsPropagatingToDep returns the conditions which propagate down
 // an edge from target to dependency.
 //
 // This function sets the policy for the top-down traversal and how conditions
@@ -167,81 +186,73 @@
 // aggregation, per policy it ceases to be a pure aggregation in the context of
 // that derivative work. The `treatAsAggregate` parameter will be false for
 // non-aggregates and for aggregates in non-aggregate contexts.
-func targetConditionsApplicableToDep(e TargetEdge, targetConditions *LicenseConditionSet, treatAsAggregate bool) *LicenseConditionSet {
-	result := targetConditions.Copy()
+func targetConditionsPropagatingToDep(lg *LicenseGraph, e *TargetEdge, targetConditions LicenseConditionSet, treatAsAggregate bool) LicenseConditionSet {
+	result := targetConditions
 
 	// reverse direction -- none of these apply to things depended-on, only to targets depending-on.
-	result.RemoveAllByName(ConditionNames{"unencumbered", "permissive", "notice", "reciprocal", "proprietary", "by_exception_only"})
+	result = result.Minus(UnencumberedCondition, PermissiveCondition, NoticeCondition, ReciprocalCondition, ProprietaryCondition, ByExceptionOnlyCondition)
 
 	if !edgeIsDerivation(e) && !edgeIsDynamicLink(e) {
 		// target is not a derivative work of dependency and is not linked to dependency
-		result.RemoveAllByName(ImpliesRestricted)
+		result = result.Difference(ImpliesRestricted)
 		return result
 	}
 	if treatAsAggregate {
 		// If the author of a pure aggregate licenses it restricted, apply restricted to immediate dependencies.
 		// Otherwise, restricted does not propagate back down to dependencies.
-		restricted := result.ByName(ImpliesRestricted).AsList()
-		for _, lc := range restricted {
-			if lc.origin.name != e.e.target {
-				result.Remove(lc)
-			}
+		if !LicenseConditionSetFromNames(e.target, e.target.proto.LicenseConditions...).MatchesAnySet(ImpliesRestricted) {
+			result = result.Difference(ImpliesRestricted)
 		}
 		return result
 	}
 	if edgeIsDerivation(e) {
 		return result
 	}
-	restricted := result.ByName(ImpliesRestricted).AsList()
-	for _, lc := range restricted {
-		hasGpl := false
-		hasLgpl := false
-		hasClasspath := false
-		hasGeneric := false
-		hasOther := false
-		for _, kind := range lc.origin.LicenseKinds() {
-			if strings.HasSuffix(kind, "-with-classpath-exception") {
-				hasClasspath = true
-			} else if anyLgpl.MatchString(kind) {
-				hasLgpl = true
-			} else if versionedGpl.MatchString(kind) {
-				hasGpl = true
-			} else if genericGpl.MatchString(kind) {
-				hasGeneric = true
-			} else if kind == "legacy_restricted" || ccBySa.MatchString(kind) {
-				hasOther = true
-			}
-		}
-		if hasOther || hasGpl {
-			continue
-		}
-		if hasClasspath && !edgeNodesAreIndependentModules(e) {
-			continue
-		}
-		if hasGeneric && !hasLgpl && !hasClasspath {
-			continue
-		}
-		result.Remove(lc)
+	result = result.Minus(WeaklyRestrictedCondition)
+	if edgeNodesAreIndependentModules(e) {
+		result = result.Minus(RestrictedClasspathExceptionCondition)
 	}
 	return result
 }
 
+// conditionsAttachingAcrossEdge returns the subset of conditions in `universe`
+// that apply across edge `e`.
+//
+// This function sets the policy for attaching actions to ancestor nodes in the
+// final resolution walk.
+func conditionsAttachingAcrossEdge(lg *LicenseGraph, e *TargetEdge, universe LicenseConditionSet) LicenseConditionSet {
+	result := universe
+	if edgeIsDerivation(e) {
+		return result
+	}
+	if !edgeIsDynamicLink(e) {
+		return NewLicenseConditionSet()
+	}
+
+	result &= LicenseConditionSet(RestrictedCondition | RestrictedClasspathExceptionCondition)
+	if 0 != (result & LicenseConditionSet(RestrictedClasspathExceptionCondition)) && edgeNodesAreIndependentModules(e) {
+		result &= LicenseConditionSet(RestrictedCondition)
+	}
+	return result
+}
+
+
 // edgeIsDynamicLink returns true for edges representing shared libraries
 // linked dynamically at runtime.
-func edgeIsDynamicLink(e TargetEdge) bool {
-	return e.e.annotations.HasAnnotation("dynamic")
+func edgeIsDynamicLink(e *TargetEdge) bool {
+	return e.annotations.HasAnnotation("dynamic")
 }
 
 // edgeIsDerivation returns true for edges where the target is a derivative
 // work of dependency.
-func edgeIsDerivation(e TargetEdge) bool {
-	isDynamic := e.e.annotations.HasAnnotation("dynamic")
-	isToolchain := e.e.annotations.HasAnnotation("toolchain")
+func edgeIsDerivation(e *TargetEdge) bool {
+	isDynamic := e.annotations.HasAnnotation("dynamic")
+	isToolchain := e.annotations.HasAnnotation("toolchain")
 	return !isDynamic && !isToolchain
 }
 
 // edgeNodesAreIndependentModules returns true for edges where the target and
 // dependency are independent modules.
-func edgeNodesAreIndependentModules(e TargetEdge) bool {
-	return e.Target().PackageName() != e.Dependency().PackageName()
+func edgeNodesAreIndependentModules(e *TargetEdge) bool {
+	return e.target.PackageName() != e.dependency.PackageName()
 }
diff --git a/tools/compliance/policy/policy_test.go b/tools/compliance/policy/policy_test.go
index aea307f..09e831c 100644
--- a/tools/compliance/policy/policy_test.go
+++ b/tools/compliance/policy/policy_test.go
@@ -34,21 +34,21 @@
 		{
 			name:                     "firstparty",
 			edge:                     annotated{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
-			expectedDepActions:       []string{"apacheLib.meta_lic:apacheLib.meta_lic:notice"},
+			expectedDepActions:       []string{},
 			expectedTargetConditions: []string{},
 		},
 		{
 			name:                     "notice",
 			edge:                     annotated{"mitBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
-			expectedDepActions:       []string{"mitLib.meta_lic:mitLib.meta_lic:notice"},
+			expectedDepActions:       []string{},
 			expectedTargetConditions: []string{},
 		},
 		{
 			name: "fponlgpl",
 			edge: annotated{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"static"}},
 			expectedDepActions: []string{
-				"apacheBin.meta_lic:lgplLib.meta_lic:restricted",
-				"lgplLib.meta_lic:lgplLib.meta_lic:restricted",
+				"apacheBin.meta_lic:lgplLib.meta_lic:restricted_allows_dynamic_linking",
+				"lgplLib.meta_lic:lgplLib.meta_lic:restricted_allows_dynamic_linking",
 			},
 			expectedTargetConditions: []string{},
 		},
@@ -86,8 +86,8 @@
 			name: "independentmodulestatic",
 			edge: annotated{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"static"}},
 			expectedDepActions: []string{
-				"apacheBin.meta_lic:gplWithClasspathException.meta_lic:restricted",
-				"gplWithClasspathException.meta_lic:gplWithClasspathException.meta_lic:restricted",
+				"apacheBin.meta_lic:gplWithClasspathException.meta_lic:restricted_with_classpath_exception",
+				"gplWithClasspathException.meta_lic:gplWithClasspathException.meta_lic:restricted_with_classpath_exception",
 			},
 			expectedTargetConditions: []string{},
 		},
@@ -95,8 +95,8 @@
 			name: "dependentmodule",
 			edge: annotated{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
 			expectedDepActions: []string{
-				"dependentModule.meta_lic:gplWithClasspathException.meta_lic:restricted",
-				"gplWithClasspathException.meta_lic:gplWithClasspathException.meta_lic:restricted",
+				"dependentModule.meta_lic:gplWithClasspathException.meta_lic:restricted_with_classpath_exception",
+				"gplWithClasspathException.meta_lic:gplWithClasspathException.meta_lic:restricted_with_classpath_exception",
 			},
 			expectedTargetConditions: []string{},
 		},
@@ -104,8 +104,8 @@
 		{
 			name:                     "lgplonfp",
 			edge:                     annotated{"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
-			expectedDepActions:       []string{"apacheLib.meta_lic:apacheLib.meta_lic:notice"},
-			expectedTargetConditions: []string{"lgplBin.meta_lic:restricted"},
+			expectedDepActions:       []string{},
+			expectedTargetConditions: []string{"lgplBin.meta_lic:restricted_allows_dynamic_linking"},
 		},
 		{
 			name:                     "lgplonfpdynamic",
@@ -116,14 +116,14 @@
 		{
 			name:                     "gplonfp",
 			edge:                     annotated{"gplBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
-			expectedDepActions:       []string{"apacheLib.meta_lic:apacheLib.meta_lic:notice"},
+			expectedDepActions:       []string{},
 			expectedTargetConditions: []string{"gplBin.meta_lic:restricted"},
 		},
 		{
 			name:                     "gplcontainer",
 			edge:                     annotated{"gplContainer.meta_lic", "apacheLib.meta_lic", []string{"static"}},
 			treatAsAggregate:         true,
-			expectedDepActions:       []string{"apacheLib.meta_lic:apacheLib.meta_lic:notice"},
+			expectedDepActions:       []string{},
 			expectedTargetConditions: []string{"gplContainer.meta_lic:restricted"},
 		},
 		{
@@ -133,7 +133,6 @@
 			otherCondition:   "gplLib.meta_lic:restricted",
 			expectedDepActions: []string{
 				"apacheContainer.meta_lic:gplLib.meta_lic:restricted",
-				"apacheLib.meta_lic:apacheLib.meta_lic:notice",
 				"apacheLib.meta_lic:gplLib.meta_lic:restricted",
 				"gplLib.meta_lic:gplLib.meta_lic:restricted",
 			},
@@ -146,7 +145,6 @@
 			otherCondition:   "gplLib.meta_lic:restricted",
 			expectedDepActions: []string{
 				"apacheBin.meta_lic:gplLib.meta_lic:restricted",
-				"apacheLib.meta_lic:apacheLib.meta_lic:notice",
 				"apacheLib.meta_lic:gplLib.meta_lic:restricted",
 				"gplLib.meta_lic:gplLib.meta_lic:restricted",
 			},
@@ -167,14 +165,14 @@
 		{
 			name:                     "independentmodulereversestatic",
 			edge:                     annotated{"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", []string{"static"}},
-			expectedDepActions:       []string{"apacheBin.meta_lic:apacheBin.meta_lic:notice"},
-			expectedTargetConditions: []string{"gplWithClasspathException.meta_lic:restricted"},
+			expectedDepActions:       []string{},
+			expectedTargetConditions: []string{"gplWithClasspathException.meta_lic:restricted_with_classpath_exception"},
 		},
 		{
 			name:                     "dependentmodulereverse",
 			edge:                     annotated{"gplWithClasspathException.meta_lic", "dependentModule.meta_lic", []string{"dynamic"}},
 			expectedDepActions:       []string{},
-			expectedTargetConditions: []string{"gplWithClasspathException.meta_lic:restricted"},
+			expectedTargetConditions: []string{"gplWithClasspathException.meta_lic:restricted_with_classpath_exception"},
 		},
 		{
 			name: "ponr",
@@ -188,31 +186,31 @@
 		{
 			name:                     "ronp",
 			edge:                     annotated{"gplBin.meta_lic", "proprietary.meta_lic", []string{"static"}},
-			expectedDepActions:       []string{"proprietary.meta_lic:proprietary.meta_lic:proprietary"},
+			expectedDepActions:       []string{},
 			expectedTargetConditions: []string{"gplBin.meta_lic:restricted"},
 		},
 		{
 			name:                     "noticeonb_e_o",
 			edge:                     annotated{"mitBin.meta_lic", "by_exception.meta_lic", []string{"static"}},
-			expectedDepActions:       []string{"by_exception.meta_lic:by_exception.meta_lic:by_exception_only"},
+			expectedDepActions:       []string{},
 			expectedTargetConditions: []string{},
 		},
 		{
 			name:                     "b_e_oonnotice",
 			edge:                     annotated{"by_exception.meta_lic", "mitLib.meta_lic", []string{"static"}},
-			expectedDepActions:       []string{"mitLib.meta_lic:mitLib.meta_lic:notice"},
+			expectedDepActions:       []string{},
 			expectedTargetConditions: []string{},
 		},
 		{
 			name:                     "noticeonrecip",
 			edge:                     annotated{"mitBin.meta_lic", "mplLib.meta_lic", []string{"static"}},
-			expectedDepActions:       []string{"mplLib.meta_lic:mplLib.meta_lic:reciprocal"},
+			expectedDepActions:       []string{},
 			expectedTargetConditions: []string{},
 		},
 		{
 			name:                     "reciponnotice",
 			edge:                     annotated{"mplBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
-			expectedDepActions:       []string{"mitLib.meta_lic:mitLib.meta_lic:notice"},
+			expectedDepActions:       []string{},
 			expectedTargetConditions: []string{},
 		},
 	}
@@ -231,69 +229,80 @@
 				t.Errorf("unexpected error reading graph: %w", err)
 				return
 			}
+			edge := lg.Edges()[0]
 			// simulate a condition inherited from another edge/dependency.
 			otherTarget := ""
 			otherCondition := ""
+			var otn *TargetNode
 			if len(tt.otherCondition) > 0 {
 				fields := strings.Split(tt.otherCondition, ":")
 				otherTarget = fields[0]
 				otherCondition = fields[1]
+				otn = &TargetNode{name: otherTarget}
 				// other target must exist in graph
-				lg.targets[otherTarget] = &TargetNode{name: otherTarget}
-				lg.targets[otherTarget].proto.LicenseConditions = append(lg.targets[otherTarget].proto.LicenseConditions, otherCondition)
+				lg.targets[otherTarget] = otn
+				otn.licenseConditions = LicenseConditionSet(RecognizedConditionNames[otherCondition])
+			}
+			targets := make(map[string]*TargetNode)
+			targets[edge.target.name] = edge.target
+			targets[edge.dependency.name] = edge.dependency
+			if otn != nil {
+				targets[otn.name] = otn
 			}
 			if tt.expectedDepActions != nil {
-				depActions := make(actionSet)
-				depActions[lg.targets[tt.edge.dep]] = lg.targets[tt.edge.dep].LicenseConditions()
-				if otherTarget != "" {
-					// simulate a sub-dependency's condition having already propagated up to dep and about to go to target
-					otherCs := lg.targets[otherTarget].LicenseConditions()
-					depActions[lg.targets[tt.edge.dep]].AddSet(otherCs)
-					depActions[lg.targets[otherTarget]] = otherCs
-				}
-				asActual := depActionsApplicableToTarget(lg.Edges()[0], depActions, tt.treatAsAggregate)
-				asExpected := make(actionSet)
-				for _, triple := range tt.expectedDepActions {
-					fields := strings.Split(triple, ":")
-					actsOn := lg.targets[fields[0]]
-					origin := lg.targets[fields[1]]
-					expectedConditions := newLicenseConditionSet()
-					expectedConditions.add(origin, fields[2:]...)
-					if _, ok := asExpected[actsOn]; ok {
-						asExpected[actsOn].AddSet(expectedConditions)
-					} else {
-						asExpected[actsOn] = expectedConditions
+				t.Run("depConditionsPropagatingToTarget", func(t *testing.T) {
+					depConditions := edge.dependency.LicenseConditions()
+					if otherTarget != "" {
+						// simulate a sub-dependency's condition having already propagated up to dep and about to go to target
+						otherCs := otn.LicenseConditions()
+						depConditions |= otherCs
 					}
-				}
-
-				checkSameActions(lg, asActual, asExpected, t)
+					t.Logf("calculate target actions for edge=%s, dep conditions=%04x, treatAsAggregate=%v", edge.String(), depConditions, tt.treatAsAggregate)
+					csActual := depConditionsPropagatingToTarget(lg, edge, depConditions, tt.treatAsAggregate)
+					t.Logf("calculated target conditions as %04x{%s}", csActual, strings.Join(csActual.Names(), ", "))
+					csExpected := NewLicenseConditionSet()
+					for _, triple := range tt.expectedDepActions {
+						fields := strings.Split(triple, ":")
+						expectedConditions := NewLicenseConditionSet()
+						for _, cname := range fields[2:] {
+							expectedConditions = expectedConditions.Plus(RecognizedConditionNames[cname])
+						}
+						csExpected |= expectedConditions
+					}
+					t.Logf("expected target conditions as %04x{%s}", csExpected, strings.Join(csExpected.Names(), ", "))
+					if csActual != csExpected {
+						t.Errorf("unexpected license conditions: got %04x, want %04x", csActual, csExpected)
+					}
+				})
 			}
 			if tt.expectedTargetConditions != nil {
-				targetConditions := lg.TargetNode(tt.edge.target).LicenseConditions()
-				if otherTarget != "" {
-					targetConditions.add(lg.targets[otherTarget], otherCondition)
-				}
-				cs := targetConditionsApplicableToDep(
-					lg.Edges()[0],
-					targetConditions,
-					tt.treatAsAggregate)
-				actual := make([]string, 0, cs.Count())
-				for _, lc := range cs.AsList() {
-					actual = append(actual, lc.asString(":"))
-				}
-				sort.Strings(actual)
-				sort.Strings(tt.expectedTargetConditions)
-				if len(actual) != len(tt.expectedTargetConditions) {
-					t.Errorf("unexpected number of target conditions: got %v with %d conditions, want %v with %d conditions",
-						actual, len(actual), tt.expectedTargetConditions, len(tt.expectedTargetConditions))
-				} else {
-					for i := 0; i < len(actual); i++ {
-						if actual[i] != tt.expectedTargetConditions[i] {
-							t.Errorf("unexpected target condition at element %d: got %q, want %q",
-								i, actual[i], tt.expectedTargetConditions[i])
+				t.Run("targetConditionsPropagatingToDep", func(t *testing.T) {
+					targetConditions := edge.target.LicenseConditions()
+					if otherTarget != "" {
+						targetConditions = targetConditions.Union(otn.licenseConditions)
+					}
+					t.Logf("calculate dep conditions for edge=%s, target conditions=%v, treatAsAggregate=%v", edge.String(), targetConditions.Names(), tt.treatAsAggregate)
+					cs := targetConditionsPropagatingToDep(lg, edge, targetConditions, tt.treatAsAggregate)
+					t.Logf("calculated dep conditions as %v", cs.Names())
+					actual := cs.Names()
+					sort.Strings(actual)
+					expected := make([]string, 0)
+					for _, expectedDepCondition := range tt.expectedTargetConditions {
+						expected = append(expected, strings.Split(expectedDepCondition, ":")[1])
+					}
+					sort.Strings(expected)
+					if len(actual) != len(expected) {
+						t.Errorf("unexpected number of target conditions: got %v with %d conditions, want %v with %d conditions",
+							actual, len(actual), expected, len(expected))
+					} else {
+						for i := 0; i < len(actual); i++ {
+							if actual[i] != expected[i] {
+								t.Errorf("unexpected target condition at element %d: got %q, want %q",
+									i, actual[i], expected[i])
+							}
 						}
 					}
-				}
+				})
 			}
 		})
 	}
diff --git a/tools/compliance/policy/resolve.go b/tools/compliance/policy/resolve.go
index c107520..336894a 100644
--- a/tools/compliance/policy/resolve.go
+++ b/tools/compliance/policy/resolve.go
@@ -14,228 +14,196 @@
 
 package compliance
 
+import (
+	"sync"
+)
+
 // ResolveBottomUpConditions performs a bottom-up walk of the LicenseGraph
 // propagating conditions up the graph as necessary according to the properties
 // of each edge and according to each license condition in question.
 //
-// Subsequent top-down walks of the graph will filter some resolutions and may
-// introduce new resolutions.
-//
 // e.g. if a "restricted" condition applies to a binary, it also applies to all
 // of the statically-linked libraries and the transitive closure of their static
 // dependencies; even if neither they nor the transitive closure of their
 // dependencies originate any "restricted" conditions. The bottom-up walk will
 // not resolve the library and its transitive closure, but the later top-down
 // walk will.
-func ResolveBottomUpConditions(lg *LicenseGraph) *ResolutionSet {
+func ResolveBottomUpConditions(lg *LicenseGraph) {
 
 	// short-cut if already walked and cached
 	lg.mu.Lock()
-	rs := lg.rsBU
+	wg := lg.wgBU
+
+	if wg != nil {
+		lg.mu.Unlock()
+		wg.Wait()
+		return
+	}
+	wg = &sync.WaitGroup{}
+	wg.Add(1)
+	lg.wgBU = wg
 	lg.mu.Unlock()
 
-	if rs != nil {
-		return rs
+	// amap identifes targets previously walked. (guarded by mu)
+	amap := make(map[*TargetNode]struct{})
+
+	// cmap identifies targets previously walked as pure aggregates. i.e. as containers
+	// (guarded by mu)
+	cmap := make(map[*TargetNode]struct{})
+	var mu sync.Mutex
+
+	var walk func(target *TargetNode, treatAsAggregate bool) LicenseConditionSet
+
+	walk = func(target *TargetNode, treatAsAggregate bool) LicenseConditionSet {
+		priorWalkResults := func() (LicenseConditionSet, bool) {
+			mu.Lock()
+			defer mu.Unlock()
+
+			if _, alreadyWalked := amap[target]; alreadyWalked {
+				if treatAsAggregate {
+					return target.resolution, true
+				}
+				if _, asAggregate := cmap[target]; !asAggregate {
+					return target.resolution, true
+				}
+				// previously walked in a pure aggregate context,
+				// needs to walk again in non-aggregate context
+				delete(cmap, target)
+			} else {
+				target.resolution |= target.licenseConditions
+				amap[target] = struct{}{}
+			}
+			if treatAsAggregate {
+				cmap[target] = struct{}{}
+			}
+			return target.resolution, false
+		}
+		cs, alreadyWalked := priorWalkResults()
+		if alreadyWalked {
+			return cs
+		}
+
+		c := make(chan LicenseConditionSet, len(target.edges))
+		// add all the conditions from all the dependencies
+		for _, edge := range target.edges {
+			go func(edge *TargetEdge) {
+				// walk dependency to get its conditions
+				cs := walk(edge.dependency, treatAsAggregate && edge.dependency.IsContainer())
+
+				// turn those into the conditions that apply to the target
+				cs = depConditionsPropagatingToTarget(lg, edge, cs, treatAsAggregate)
+
+				c <- cs
+			}(edge)
+		}
+		for i := 0; i < len(target.edges); i++ {
+			cs |= <-c
+		}
+		mu.Lock()
+		target.resolution |= cs
+		mu.Unlock()
+
+		// return conditions up the tree
+		return cs
 	}
 
-	// must be indexed for fast lookup
-	lg.indexForward()
-
-	rs = resolveBottomUp(lg, make(map[*TargetNode]actionSet) /* empty map; no prior resolves */)
-
-	// if not yet cached, save the result
-	lg.mu.Lock()
-	if lg.rsBU == nil {
-		lg.rsBU = rs
-	} else {
-		// if we end up with 2, release the later for garbage collection
-		rs = lg.rsBU
+	// walk each of the roots
+	for _, rname := range lg.rootFiles {
+		rnode := lg.targets[rname]
+		_ = walk(rnode, rnode.IsContainer())
 	}
-	lg.mu.Unlock()
 
-	return rs
+	wg.Done()
 }
 
 // ResolveTopDownCondtions performs a top-down walk of the LicenseGraph
-// resolving all reachable nodes for `condition`. Policy establishes the rules
-// for transforming and propagating resolutions down the graph.
+// propagating conditions from target to dependency.
 //
 // e.g. For current policy, none of the conditions propagate from target to
 // dependency except restricted. For restricted, the policy is to share the
 // source of any libraries linked to restricted code and to provide notice.
-func ResolveTopDownConditions(lg *LicenseGraph) *ResolutionSet {
+func ResolveTopDownConditions(lg *LicenseGraph) {
 
 	// short-cut if already walked and cached
 	lg.mu.Lock()
-	rs := lg.rsTD
+	wg := lg.wgTD
+
+	if wg != nil {
+		lg.mu.Unlock()
+		wg.Wait()
+		return
+	}
+	wg = &sync.WaitGroup{}
+	wg.Add(1)
+	lg.wgTD = wg
 	lg.mu.Unlock()
 
-	if rs != nil {
-		return rs
-	}
-
 	// start with the conditions propagated up the graph
-	rs = ResolveBottomUpConditions(lg)
+	ResolveBottomUpConditions(lg)
 
-	// rmap maps 'appliesTo' targets to their applicable conditions
-	//
-	// rmap is the resulting ResolutionSet
-	rmap := make(map[*TargetNode]actionSet)
+	// amap contains the set of targets already walked. (guarded by mu)
+	amap := make(map[*TargetNode]struct{})
 
 	// cmap contains the set of targets walked as pure aggregates. i.e. containers
+	// (guarded by mu)
 	cmap := make(map[*TargetNode]struct{})
 
-	var walk func(fnode *TargetNode, cs *LicenseConditionSet, treatAsAggregate bool)
+	// mu guards concurrent access to cmap
+	var mu sync.Mutex
 
-	walk = func(fnode *TargetNode, cs *LicenseConditionSet, treatAsAggregate bool) {
-		if _, ok := rmap[fnode]; !ok {
-			rmap[fnode] = make(actionSet)
-		}
-		rmap[fnode].add(fnode, cs)
+	var walk func(fnode *TargetNode, cs LicenseConditionSet, treatAsAggregate bool)
+
+	walk = func(fnode *TargetNode, cs LicenseConditionSet, treatAsAggregate bool) {
+		defer wg.Done()
+		mu.Lock()
+		fnode.resolution |= fnode.licenseConditions
+		fnode.resolution |= cs
+		amap[fnode] = struct{}{}
 		if treatAsAggregate {
 			cmap[fnode] = struct{}{}
 		}
-		// add conditions attached to `fnode`
-		cs = cs.Copy()
-		for _, fcs := range rs.resolutions[fnode] {
-			cs.AddSet(fcs)
-		}
+		cs = fnode.resolution
+		mu.Unlock()
 		// for each dependency
-		for _, edge := range lg.index[fnode.name] {
-			e := TargetEdge{lg, edge}
-			// dcs holds the dpendency conditions inherited from the target
-			dcs := targetConditionsApplicableToDep(e, cs, treatAsAggregate)
-			if dcs.IsEmpty() && !treatAsAggregate {
-				continue
-			}
-			dnode := lg.targets[edge.dependency]
-			if as, alreadyWalked := rmap[dnode]; alreadyWalked {
-				diff := dcs.Copy()
-				diff.RemoveSet(as.conditions())
-				if diff.IsEmpty() {
-					// no new conditions
+		for _, edge := range fnode.edges {
+			func(edge *TargetEdge) {
+				// dcs holds the dpendency conditions inherited from the target
+				dcs := targetConditionsPropagatingToDep(lg, edge, cs, treatAsAggregate)
+				dnode := edge.dependency
+				mu.Lock()
+				defer mu.Unlock()
+				depcs := dnode.resolution
+				_, alreadyWalked := amap[dnode]
+				if !dcs.IsEmpty() && alreadyWalked {
+					if dcs.Difference(depcs).IsEmpty() {
+						// no new conditions
 
-					// pure aggregates never need walking a 2nd time with same conditions
-					if treatAsAggregate {
-						continue
+						// pure aggregates never need walking a 2nd time with same conditions
+						if treatAsAggregate {
+							return
+						}
+						// non-aggregates don't need walking as non-aggregate a 2nd time
+						if _, asAggregate := cmap[dnode]; !asAggregate {
+							return
+						}
+						// previously walked as pure aggregate; need to re-walk as non-aggregate
+						delete(cmap, dnode)
 					}
-					// non-aggregates don't need walking as non-aggregate a 2nd time
-					if _, asAggregate := cmap[dnode]; !asAggregate {
-						continue
-					}
-					// previously walked as pure aggregate; need to re-walk as non-aggregate
-					delete(cmap, dnode)
 				}
-			}
-			// add the conditions to the dependency
-			walk(dnode, dcs, treatAsAggregate && lg.targets[edge.dependency].IsContainer())
+				// add the conditions to the dependency
+				wg.Add(1)
+				go walk(dnode, dcs, treatAsAggregate && dnode.IsContainer())
+			}(edge)
 		}
 	}
 
 	// walk each of the roots
-	for _, r := range lg.rootFiles {
-		rnode := lg.targets[r]
-		as, ok := rs.resolutions[rnode]
-		if !ok {
-			// no conditions in root or transitive closure of dependencies
-			continue
-		}
-		if as.isEmpty() {
-			continue
-		}
-
+	for _, rname := range lg.rootFiles {
+		rnode := lg.targets[rname]
+		wg.Add(1)
 		// add the conditions to the root and its transitive closure
-		walk(rnode, newLicenseConditionSet(), lg.targets[r].IsContainer())
+		go walk(rnode, NewLicenseConditionSet(), rnode.IsContainer())
 	}
-
-	// back-fill any bottom-up conditions on targets missed by top-down walk
-	for attachesTo, as := range rs.resolutions {
-		if _, ok := rmap[attachesTo]; !ok {
-			rmap[attachesTo] = as.copy()
-		} else {
-			rmap[attachesTo].addSet(as)
-		}
-	}
-
-	// propagate any new conditions back up the graph
-	rs = resolveBottomUp(lg, rmap)
-
-	// if not yet cached, save the result
-	lg.mu.Lock()
-	if lg.rsTD == nil {
-		lg.rsTD = rs
-	} else {
-		// if we end up with 2, release the later for garbage collection
-		rs = lg.rsTD
-	}
-	lg.mu.Unlock()
-
-	return rs
-}
-
-// resolveBottomUp implements a bottom-up resolve propagating conditions both
-// from the graph, and from a `priors` map of resolutions.
-func resolveBottomUp(lg *LicenseGraph, priors map[*TargetNode]actionSet) *ResolutionSet {
-	rs := newResolutionSet()
-
-	// cmap contains an entry for every target that was previously walked as a pure aggregate only.
-	cmap := make(map[string]struct{})
-
-	var walk func(f string, treatAsAggregate bool) actionSet
-
-	walk = func(f string, treatAsAggregate bool) actionSet {
-		target := lg.targets[f]
-		result := make(actionSet)
-		result[target] = newLicenseConditionSet()
-		result[target].add(target, target.proto.LicenseConditions...)
-		if pas, ok := priors[target]; ok {
-			result.addSet(pas)
-		}
-		if preresolved, ok := rs.resolutions[target]; ok {
-			if treatAsAggregate {
-				result.addSet(preresolved)
-				return result
-			}
-			if _, asAggregate := cmap[f]; !asAggregate {
-				result.addSet(preresolved)
-				return result
-			}
-			// previously walked in a pure aggregate context,
-			// needs to walk again in non-aggregate context
-			delete(cmap, f)
-		}
-		if treatAsAggregate {
-			cmap[f] = struct{}{}
-		}
-
-		// add all the conditions from all the dependencies
-		for _, edge := range lg.index[f] {
-			// walk dependency to get its conditions
-			as := walk(edge.dependency, treatAsAggregate && lg.targets[edge.dependency].IsContainer())
-
-			// turn those into the conditions that apply to the target
-			as = depActionsApplicableToTarget(TargetEdge{lg, edge}, as, treatAsAggregate)
-
-			// add them to the result
-			result.addSet(as)
-		}
-
-		// record these conditions as applicable to the target
-		rs.addConditions(target, result)
-		if len(priors) == 0 {
-			// on the first bottom-up resolve, parents have their own sharing and notice needs
-			// on the later resolve, if priors is empty, there will be nothing new to add
-			rs.addSelf(target, result.byName(ImpliesRestricted))
-		}
-
-		// return this up the tree
-		return result
-	}
-
-	// walk each of the roots
-	for _, r := range lg.rootFiles {
-		_ = walk(r, lg.targets[r].IsContainer())
-	}
-
-	return rs
+	wg.Done()
+	wg.Wait()
 }
diff --git a/tools/compliance/policy/resolve_test.go b/tools/compliance/policy/resolve_test.go
index 4c99d35..09dd7dd 100644
--- a/tools/compliance/policy/resolve_test.go
+++ b/tools/compliance/policy/resolve_test.go
@@ -16,15 +16,16 @@
 
 import (
 	"bytes"
+	"sort"
 	"testing"
 )
 
 func TestResolveBottomUpConditions(t *testing.T) {
 	tests := []struct {
-		name                string
-		roots               []string
-		edges               []annotated
-		expectedResolutions []res
+		name            string
+		roots           []string
+		edges           []annotated
+		expectedActions []tcond
 	}{
 		{
 			name:  "firstparty",
@@ -32,10 +33,9 @@
 			edges: []annotated{
 				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
 			},
-			expectedResolutions: []res{
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheBin.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
-				{"apacheLib.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+			expectedActions: []tcond{
+				{"apacheBin.meta_lic", "notice"},
+				{"apacheLib.meta_lic", "notice"},
 			},
 		},
 		{
@@ -44,9 +44,9 @@
 			edges: []annotated{
 				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"toolchain"}},
 			},
-			expectedResolutions: []res{
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheLib.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+			expectedActions: []tcond{
+				{"apacheBin.meta_lic", "notice"},
+				{"apacheLib.meta_lic", "notice"},
 			},
 		},
 		{
@@ -56,13 +56,10 @@
 				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
 				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
 			},
-			expectedResolutions: []res{
-				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
-				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheContainer.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheBin.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
-				{"apacheLib.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+			expectedActions: []tcond{
+				{"apacheContainer.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "notice"},
+				{"apacheLib.meta_lic", "notice"},
 			},
 		},
 		{
@@ -72,12 +69,10 @@
 				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
 				{"apacheContainer.meta_lic", "apacheLib.meta_lic", []string{"static"}},
 			},
-			expectedResolutions: []res{
-				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
-				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheContainer.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheLib.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+			expectedActions: []tcond{
+				{"apacheContainer.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "notice"},
+				{"apacheLib.meta_lic", "notice"},
 			},
 		},
 		{
@@ -86,9 +81,9 @@
 			edges: []annotated{
 				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
 			},
-			expectedResolutions: []res{
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheLib.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+			expectedActions: []tcond{
+				{"apacheBin.meta_lic", "notice"},
+				{"apacheLib.meta_lic", "notice"},
 			},
 		},
 		{
@@ -98,11 +93,10 @@
 				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
 				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
 			},
-			expectedResolutions: []res{
-				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
-				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheLib.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+			expectedActions: []tcond{
+				{"apacheContainer.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "notice"},
+				{"apacheLib.meta_lic", "notice"},
 			},
 		},
 		{
@@ -112,11 +106,10 @@
 				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
 				{"apacheContainer.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
 			},
-			expectedResolutions: []res{
-				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
-				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheLib.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+			expectedActions: []tcond{
+				{"apacheContainer.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "notice"},
+				{"apacheLib.meta_lic", "notice"},
 			},
 		},
 		{
@@ -125,11 +118,9 @@
 			edges: []annotated{
 				{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"static"}},
 			},
-			expectedResolutions: []res{
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"},
-				{"apacheBin.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
-				{"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+			expectedActions: []tcond{
+				{"apacheBin.meta_lic", "notice|restricted"},
+				{"gplLib.meta_lic", "restricted"},
 			},
 		},
 		{
@@ -138,9 +129,9 @@
 			edges: []annotated{
 				{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"toolchain"}},
 			},
-			expectedResolutions: []res{
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+			expectedActions: []tcond{
+				{"apacheBin.meta_lic", "notice"},
+				{"gplLib.meta_lic", "restricted"},
 			},
 		},
 		{
@@ -150,16 +141,10 @@
 				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
 				{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"static"}},
 			},
-			expectedResolutions: []res{
-				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
-				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"},
-				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"},
-				{"apacheContainer.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"},
-				{"apacheBin.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
-				{"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+			expectedActions: []tcond{
+				{"apacheContainer.meta_lic", "notice|restricted"},
+				{"apacheBin.meta_lic", "notice|restricted"},
+				{"gplLib.meta_lic", "restricted"},
 			},
 		},
 		{
@@ -169,13 +154,10 @@
 				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
 				{"apacheContainer.meta_lic", "gplLib.meta_lic", []string{"static"}},
 			},
-			expectedResolutions: []res{
-				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
-				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"},
-				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheContainer.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+			expectedActions: []tcond{
+				{"apacheContainer.meta_lic", "notice|restricted"},
+				{"apacheBin.meta_lic", "notice"},
+				{"gplLib.meta_lic", "restricted"},
 			},
 		},
 		{
@@ -184,11 +166,9 @@
 			edges: []annotated{
 				{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"dynamic"}},
 			},
-			expectedResolutions: []res{
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"},
-				{"apacheBin.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
-				{"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+			expectedActions: []tcond{
+				{"apacheBin.meta_lic", "notice|restricted"},
+				{"gplLib.meta_lic", "restricted"},
 			},
 		},
 		{
@@ -198,16 +178,10 @@
 				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
 				{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"dynamic"}},
 			},
-			expectedResolutions: []res{
-				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
-				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"},
-				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"},
-				{"apacheContainer.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"},
-				{"apacheBin.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
-				{"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+			expectedActions: []tcond{
+				{"apacheContainer.meta_lic", "notice|restricted"},
+				{"apacheBin.meta_lic", "notice|restricted"},
+				{"gplLib.meta_lic", "restricted"},
 			},
 		},
 		{
@@ -217,13 +191,10 @@
 				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
 				{"apacheContainer.meta_lic", "gplLib.meta_lic", []string{"dynamic"}},
 			},
-			expectedResolutions: []res{
-				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
-				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"},
-				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheContainer.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+			expectedActions: []tcond{
+				{"apacheContainer.meta_lic", "notice|restricted"},
+				{"apacheBin.meta_lic", "notice"},
+				{"gplLib.meta_lic", "restricted"},
 			},
 		},
 		{
@@ -232,11 +203,9 @@
 			edges: []annotated{
 				{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"static"}},
 			},
-			expectedResolutions: []res{
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "lgplLib.meta_lic", "restricted"},
-				{"apacheBin.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
-				{"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+			expectedActions: []tcond{
+				{"apacheBin.meta_lic", "notice|restricted_allows_dynamic_linking"},
+				{"lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
 			},
 		},
 		{
@@ -245,9 +214,9 @@
 			edges: []annotated{
 				{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"toolchain"}},
 			},
-			expectedResolutions: []res{
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+			expectedActions: []tcond{
+				{"apacheBin.meta_lic", "notice"},
+				{"lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
 			},
 		},
 		{
@@ -257,16 +226,10 @@
 				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
 				{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"static"}},
 			},
-			expectedResolutions: []res{
-				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
-				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "lgplLib.meta_lic", "restricted"},
-				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "lgplLib.meta_lic", "restricted"},
-				{"apacheContainer.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "lgplLib.meta_lic", "restricted"},
-				{"apacheBin.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
-				{"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+			expectedActions: []tcond{
+				{"apacheContainer.meta_lic", "notice|restricted_allows_dynamic_linking"},
+				{"apacheBin.meta_lic", "notice|restricted_allows_dynamic_linking"},
+				{"lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
 			},
 		},
 		{
@@ -276,13 +239,10 @@
 				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
 				{"apacheContainer.meta_lic", "lgplLib.meta_lic", []string{"static"}},
 			},
-			expectedResolutions: []res{
-				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
-				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "lgplLib.meta_lic", "restricted"},
-				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheContainer.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+			expectedActions: []tcond{
+				{"apacheContainer.meta_lic", "notice|restricted_allows_dynamic_linking"},
+				{"apacheBin.meta_lic", "notice"},
+				{"lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
 			},
 		},
 		{
@@ -291,9 +251,9 @@
 			edges: []annotated{
 				{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}},
 			},
-			expectedResolutions: []res{
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+			expectedActions: []tcond{
+				{"apacheBin.meta_lic", "notice"},
+				{"lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
 			},
 		},
 		{
@@ -303,11 +263,10 @@
 				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
 				{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}},
 			},
-			expectedResolutions: []res{
-				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
-				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+			expectedActions: []tcond{
+				{"apacheContainer.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "notice"},
+				{"lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
 			},
 		},
 		{
@@ -317,11 +276,10 @@
 				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
 				{"apacheContainer.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}},
 			},
-			expectedResolutions: []res{
-				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
-				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+			expectedActions: []tcond{
+				{"apacheContainer.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "notice"},
+				{"lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
 			},
 		},
 		{
@@ -330,11 +288,9 @@
 			edges: []annotated{
 				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"static"}},
 			},
-			expectedResolutions: []res{
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
-				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
-				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			expectedActions: []tcond{
+				{"apacheBin.meta_lic", "notice|restricted_with_classpath_exception"},
+				{"gplWithClasspathException.meta_lic", "restricted_with_classpath_exception"},
 			},
 		},
 		{
@@ -343,11 +299,9 @@
 			edges: []annotated{
 				{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", []string{"static"}},
 			},
-			expectedResolutions: []res{
-				{"dependentModule.meta_lic", "dependentModule.meta_lic", "dependentModule.meta_lic", "notice"},
-				{"dependentModule.meta_lic", "dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
-				{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
-				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			expectedActions: []tcond{
+				{"dependentModule.meta_lic", "notice|restricted_with_classpath_exception"},
+				{"gplWithClasspathException.meta_lic", "restricted_with_classpath_exception"},
 			},
 		},
 		{
@@ -356,9 +310,9 @@
 			edges: []annotated{
 				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
 			},
-			expectedResolutions: []res{
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			expectedActions: []tcond{
+				{"apacheBin.meta_lic", "notice"},
+				{"gplWithClasspathException.meta_lic", "restricted_with_classpath_exception"},
 			},
 		},
 		{
@@ -367,11 +321,9 @@
 			edges: []annotated{
 				{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
 			},
-			expectedResolutions: []res{
-				{"dependentModule.meta_lic", "dependentModule.meta_lic", "dependentModule.meta_lic", "notice"},
-				{"dependentModule.meta_lic", "dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
-				{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
-				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			expectedActions: []tcond{
+				{"dependentModule.meta_lic", "notice|restricted_with_classpath_exception"},
+				{"gplWithClasspathException.meta_lic", "restricted_with_classpath_exception"},
 			},
 		},
 	}
@@ -383,19 +335,37 @@
 				t.Errorf("unexpected test data error: got %w, want no error", err)
 				return
 			}
-			expectedRs := toResolutionSet(lg, tt.expectedResolutions)
-			actualRs := ResolveBottomUpConditions(lg)
-			checkSame(actualRs, expectedRs, t)
+
+			logGraph(lg, t)
+
+			ResolveBottomUpConditions(lg)
+			actual := asActionList(lg)
+			sort.Sort(actual)
+			t.Logf("actual: %s", actual.String())
+
+			expected := toActionList(lg, tt.expectedActions)
+			sort.Sort(expected)
+			t.Logf("expected: %s", expected.String())
+
+			if len(actual) != len(expected) {
+				t.Errorf("unexpected number of actions: got %d, want %d", len(actual), len(expected))
+				return
+			}
+			for i := 0; i < len(actual); i++ {
+				if actual[i] != expected[i] {
+					t.Errorf("unexpected action at index %d: got %s, want %s", i, actual[i].String(), expected[i].String())
+				}
+			}
 		})
 	}
 }
 
 func TestResolveTopDownConditions(t *testing.T) {
 	tests := []struct {
-		name                string
-		roots               []string
-		edges               []annotated
-		expectedResolutions []res
+		name            string
+		roots           []string
+		edges           []annotated
+		expectedActions []tcond
 	}{
 		{
 			name:  "firstparty",
@@ -403,10 +373,9 @@
 			edges: []annotated{
 				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
 			},
-			expectedResolutions: []res{
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheBin.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
-				{"apacheLib.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+			expectedActions: []tcond{
+				{"apacheBin.meta_lic", "notice"},
+				{"apacheLib.meta_lic", "notice"},
 			},
 		},
 		{
@@ -415,9 +384,9 @@
 			edges: []annotated{
 				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
 			},
-			expectedResolutions: []res{
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheLib.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+			expectedActions: []tcond{
+				{"apacheBin.meta_lic", "notice"},
+				{"apacheLib.meta_lic", "notice"},
 			},
 		},
 		{
@@ -427,15 +396,10 @@
 				{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"static"}},
 				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
 			},
-			expectedResolutions: []res{
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"},
-				{"apacheBin.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
-				{"apacheBin.meta_lic", "mitLib.meta_lic", "gplLib.meta_lic", "restricted"},
-				{"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
-				{"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
-				{"mitLib.meta_lic", "mitLib.meta_lic", "gplLib.meta_lic", "restricted"},
-				{"mitLib.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+			expectedActions: []tcond{
+				{"apacheBin.meta_lic", "notice|restricted"},
+				{"mitLib.meta_lic", "notice|restricted"},
+				{"gplLib.meta_lic", "restricted"},
 			},
 		},
 		{
@@ -445,11 +409,10 @@
 				{"apacheBin.meta_lic", "gplBin.meta_lic", []string{"toolchain"}},
 				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
 			},
-			expectedResolutions: []res{
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
-				{"gplBin.meta_lic", "gplBin.meta_lic", "gplBin.meta_lic", "restricted"},
-				{"mitLib.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+			expectedActions: []tcond{
+				{"apacheBin.meta_lic", "notice"},
+				{"mitLib.meta_lic", "notice"},
+				{"gplBin.meta_lic", "restricted"},
 			},
 		},
 		{
@@ -462,27 +425,13 @@
 				{"apacheBin.meta_lic", "mplLib.meta_lic", []string{"static"}},
 				{"mitBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
 			},
-			expectedResolutions: []res{
-				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
-				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"},
-				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"},
-				{"apacheContainer.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
-				{"apacheContainer.meta_lic", "mitBin.meta_lic", "mitBin.meta_lic", "notice"},
-				{"apacheContainer.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
-				{"apacheContainer.meta_lic", "mplLib.meta_lic", "gplLib.meta_lic", "restricted"},
-				{"apacheContainer.meta_lic", "mplLib.meta_lic", "mplLib.meta_lic", "reciprocal"},
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"},
-				{"apacheBin.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
-				{"apacheBin.meta_lic", "mplLib.meta_lic", "gplLib.meta_lic", "restricted"},
-				{"apacheBin.meta_lic", "mplLib.meta_lic", "mplLib.meta_lic", "reciprocal"},
-				{"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
-				{"mitBin.meta_lic", "mitBin.meta_lic", "mitBin.meta_lic", "notice"},
-				{"mitBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
-				{"mitLib.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
-				{"mplLib.meta_lic", "mplLib.meta_lic", "gplLib.meta_lic", "restricted"},
-				{"mplLib.meta_lic", "mplLib.meta_lic", "mplLib.meta_lic", "reciprocal"},
+			expectedActions: []tcond{
+				{"apacheContainer.meta_lic", "notice|restricted"},
+				{"apacheBin.meta_lic", "notice|restricted"},
+				{"mitBin.meta_lic", "notice"},
+				{"gplLib.meta_lic", "restricted"},
+				{"mplLib.meta_lic", "reciprocal|restricted"},
+				{"mitLib.meta_lic", "notice"},
 			},
 		},
 		{
@@ -492,13 +441,10 @@
 				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
 				{"apacheContainer.meta_lic", "gplLib.meta_lic", []string{"static"}},
 			},
-			expectedResolutions: []res{
-				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
-				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"},
-				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheContainer.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+			expectedActions: []tcond{
+				{"apacheContainer.meta_lic", "notice|restricted"},
+				{"apacheBin.meta_lic", "notice"},
+				{"gplLib.meta_lic", "restricted"},
 			},
 		},
 		{
@@ -508,14 +454,10 @@
 				{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"dynamic"}},
 				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"dynamic"}},
 			},
-			expectedResolutions: []res{
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"},
-				{"apacheBin.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
-				{"apacheBin.meta_lic", "mitLib.meta_lic", "gplLib.meta_lic", "restricted"},
-				{"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
-				{"mitLib.meta_lic", "mitLib.meta_lic", "gplLib.meta_lic", "restricted"},
-				{"mitLib.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+			expectedActions: []tcond{
+				{"apacheBin.meta_lic", "notice|restricted"},
+				{"gplLib.meta_lic", "restricted"},
+				{"mitLib.meta_lic", "notice|restricted"},
 			},
 		},
 		{
@@ -528,23 +470,13 @@
 				{"apacheBin.meta_lic", "mplLib.meta_lic", []string{"dynamic"}},
 				{"mitBin.meta_lic", "mitLib.meta_lic", []string{"dynamic"}},
 			},
-			expectedResolutions: []res{
-				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
-				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"},
-				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"},
-				{"apacheContainer.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
-				{"apacheContainer.meta_lic", "mitBin.meta_lic", "mitBin.meta_lic", "notice"},
-				{"apacheContainer.meta_lic", "mplLib.meta_lic", "gplLib.meta_lic", "restricted"},
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"},
-				{"apacheBin.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
-				{"apacheBin.meta_lic", "mplLib.meta_lic", "gplLib.meta_lic", "restricted"},
-				{"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
-				{"mitBin.meta_lic", "mitBin.meta_lic", "mitBin.meta_lic", "notice"},
-				{"mitLib.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
-				{"mplLib.meta_lic", "mplLib.meta_lic", "gplLib.meta_lic", "restricted"},
-				{"mplLib.meta_lic", "mplLib.meta_lic", "mplLib.meta_lic", "reciprocal"},
+			expectedActions: []tcond{
+				{"apacheContainer.meta_lic", "notice|restricted"},
+				{"apacheBin.meta_lic", "notice|restricted"},
+				{"mitBin.meta_lic", "notice"},
+				{"gplLib.meta_lic", "restricted"},
+				{"mplLib.meta_lic", "reciprocal|restricted"},
+				{"mitLib.meta_lic", "notice"},
 			},
 		},
 		{
@@ -554,13 +486,10 @@
 				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
 				{"apacheContainer.meta_lic", "gplLib.meta_lic", []string{"dynamic"}},
 			},
-			expectedResolutions: []res{
-				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
-				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"},
-				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheContainer.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+			expectedActions: []tcond{
+				{"apacheContainer.meta_lic", "notice|restricted"},
+				{"apacheBin.meta_lic", "notice"},
+				{"gplLib.meta_lic", "restricted"},
 			},
 		},
 		{
@@ -570,15 +499,10 @@
 				{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"static"}},
 				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
 			},
-			expectedResolutions: []res{
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "lgplLib.meta_lic", "restricted"},
-				{"apacheBin.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
-				{"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
-				{"apacheBin.meta_lic", "mitLib.meta_lic", "lgplLib.meta_lic", "restricted"},
-				{"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
-				{"mitLib.meta_lic", "mitLib.meta_lic", "lgplLib.meta_lic", "restricted"},
-				{"mitLib.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+			expectedActions: []tcond{
+				{"apacheBin.meta_lic", "notice|restricted_allows_dynamic_linking"},
+				{"lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
+				{"mitLib.meta_lic", "notice|restricted_allows_dynamic_linking"},
 			},
 		},
 		{
@@ -588,11 +512,10 @@
 				{"apacheBin.meta_lic", "lgplBin.meta_lic", []string{"toolchain"}},
 				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
 			},
-			expectedResolutions: []res{
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
-				{"lgplBin.meta_lic", "lgplBin.meta_lic", "lgplBin.meta_lic", "restricted"},
-				{"mitLib.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+			expectedActions: []tcond{
+				{"apacheBin.meta_lic", "notice"},
+				{"lgplBin.meta_lic", "restricted_allows_dynamic_linking"},
+				{"mitLib.meta_lic", "notice"},
 			},
 		},
 		{
@@ -603,22 +526,11 @@
 				{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"static"}},
 				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
 			},
-			expectedResolutions: []res{
-				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
-				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "lgplLib.meta_lic", "restricted"},
-				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "lgplLib.meta_lic", "restricted"},
-				{"apacheContainer.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
-				{"apacheContainer.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
-				{"apacheContainer.meta_lic", "mitLib.meta_lic", "lgplLib.meta_lic", "restricted"},
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "lgplLib.meta_lic", "restricted"},
-				{"apacheBin.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
-				{"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
-				{"apacheBin.meta_lic", "mitLib.meta_lic", "lgplLib.meta_lic", "restricted"},
-				{"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
-				{"mitLib.meta_lic", "mitLib.meta_lic", "lgplLib.meta_lic", "restricted"},
-				{"mitLib.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+			expectedActions: []tcond{
+				{"apacheContainer.meta_lic", "notice|restricted_allows_dynamic_linking"},
+				{"apacheBin.meta_lic", "notice|restricted_allows_dynamic_linking"},
+				{"lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
+				{"mitLib.meta_lic", "notice|restricted_allows_dynamic_linking"},
 			},
 		},
 		{
@@ -628,13 +540,10 @@
 				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
 				{"apacheContainer.meta_lic", "lgplLib.meta_lic", []string{"static"}},
 			},
-			expectedResolutions: []res{
-				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
-				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "lgplLib.meta_lic", "restricted"},
-				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheContainer.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+			expectedActions: []tcond{
+				{"apacheContainer.meta_lic", "notice|restricted_allows_dynamic_linking"},
+				{"apacheBin.meta_lic", "notice"},
+				{"lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
 			},
 		},
 		{
@@ -644,11 +553,10 @@
 				{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}},
 				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
 			},
-			expectedResolutions: []res{
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
-				{"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
-				{"mitLib.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+			expectedActions: []tcond{
+				{"apacheBin.meta_lic", "notice"},
+				{"lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
+				{"mitLib.meta_lic", "notice"},
 			},
 		},
 		{
@@ -658,11 +566,10 @@
 				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
 				{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}},
 			},
-			expectedResolutions: []res{
-				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
-				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+			expectedActions: []tcond{
+				{"apacheContainer.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "notice"},
+				{"lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
 			},
 		},
 		{
@@ -672,11 +579,10 @@
 				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
 				{"apacheContainer.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}},
 			},
-			expectedResolutions: []res{
-				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
-				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+			expectedActions: []tcond{
+				{"apacheContainer.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "notice"},
+				{"lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
 			},
 		},
 		{
@@ -686,15 +592,10 @@
 				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"static"}},
 				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
 			},
-			expectedResolutions: []res{
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
-				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
-				{"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
-				{"apacheBin.meta_lic", "mitLib.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
-				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
-				{"mitLib.meta_lic", "mitLib.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
-				{"mitLib.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+			expectedActions: []tcond{
+				{"apacheBin.meta_lic", "notice|restricted_with_classpath_exception"},
+				{"gplWithClasspathException.meta_lic", "restricted_with_classpath_exception"},
+				{"mitLib.meta_lic", "notice|restricted_with_classpath_exception"},
 			},
 		},
 		{
@@ -704,15 +605,10 @@
 				{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", []string{"static"}},
 				{"dependentModule.meta_lic", "mitLib.meta_lic", []string{"static"}},
 			},
-			expectedResolutions: []res{
-				{"dependentModule.meta_lic", "dependentModule.meta_lic", "dependentModule.meta_lic", "notice"},
-				{"dependentModule.meta_lic", "dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
-				{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
-				{"dependentModule.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
-				{"dependentModule.meta_lic", "mitLib.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
-				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
-				{"mitLib.meta_lic", "mitLib.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
-				{"mitLib.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+			expectedActions: []tcond{
+				{"dependentModule.meta_lic", "notice|restricted_with_classpath_exception"},
+				{"gplWithClasspathException.meta_lic", "restricted_with_classpath_exception"},
+				{"mitLib.meta_lic", "notice|restricted_with_classpath_exception"},
 			},
 		},
 		{
@@ -722,11 +618,10 @@
 				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
 				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
 			},
-			expectedResolutions: []res{
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
-				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
-				{"mitLib.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+			expectedActions: []tcond{
+				{"apacheBin.meta_lic", "notice"},
+				{"gplWithClasspathException.meta_lic", "restricted_with_classpath_exception"},
+				{"mitLib.meta_lic", "notice"},
 			},
 		},
 		{
@@ -736,15 +631,10 @@
 				{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
 				{"dependentModule.meta_lic", "mitLib.meta_lic", []string{"static"}},
 			},
-			expectedResolutions: []res{
-				{"dependentModule.meta_lic", "dependentModule.meta_lic", "dependentModule.meta_lic", "notice"},
-				{"dependentModule.meta_lic", "dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
-				{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
-				{"dependentModule.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
-				{"dependentModule.meta_lic", "mitLib.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
-				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
-				{"mitLib.meta_lic", "mitLib.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
-				{"mitLib.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+			expectedActions: []tcond{
+				{"dependentModule.meta_lic", "notice|restricted_with_classpath_exception"},
+				{"gplWithClasspathException.meta_lic", "restricted_with_classpath_exception"},
+				{"mitLib.meta_lic", "notice|restricted_with_classpath_exception"},
 			},
 		},
 	}
@@ -756,9 +646,27 @@
 				t.Errorf("unexpected test data error: got %w, want no error", err)
 				return
 			}
-			expectedRs := toResolutionSet(lg, tt.expectedResolutions)
-			actualRs := ResolveTopDownConditions(lg)
-			checkSame(actualRs, expectedRs, t)
+
+			logGraph(lg, t)
+
+			ResolveTopDownConditions(lg)
+			actual := asActionList(lg)
+			sort.Sort(actual)
+			t.Logf("actual: %s", actual.String())
+
+			expected := toActionList(lg, tt.expectedActions)
+			sort.Sort(expected)
+			t.Logf("expected: %s", expected.String())
+
+			if len(actual) != len(expected) {
+				t.Errorf("unexpected number of actions: got %d, want %d", len(actual), len(expected))
+				return
+			}
+			for i := 0; i < len(actual); i++ {
+				if actual[i] != expected[i] {
+					t.Errorf("unexpected action at index %d: got %s, want %s", i, actual[i].String(), expected[i].String())
+				}
+			}
 		})
 	}
 }
diff --git a/tools/compliance/policy/resolvenotices.go b/tools/compliance/policy/resolvenotices.go
index 80b5e02..99f6d42 100644
--- a/tools/compliance/policy/resolvenotices.go
+++ b/tools/compliance/policy/resolvenotices.go
@@ -15,7 +15,7 @@
 package compliance
 
 // ResolveNotices implements the policy for notices.
-func ResolveNotices(lg *LicenseGraph) *ResolutionSet {
-	rs := ResolveTopDownConditions(lg)
-	return WalkResolutionsForCondition(lg, rs, ImpliesNotice)
+func ResolveNotices(lg *LicenseGraph) ResolutionSet {
+	ResolveTopDownConditions(lg)
+	return WalkResolutionsForCondition(lg, ImpliesNotice)
 }
diff --git a/tools/compliance/policy/resolveprivacy.go b/tools/compliance/policy/resolveprivacy.go
index dabbc62..2a7992e 100644
--- a/tools/compliance/policy/resolveprivacy.go
+++ b/tools/compliance/policy/resolveprivacy.go
@@ -15,7 +15,7 @@
 package compliance
 
 // ResolveSourcePrivacy implements the policy for source privacy.
-func ResolveSourcePrivacy(lg *LicenseGraph) *ResolutionSet {
-	rs := ResolveTopDownConditions(lg)
-	return WalkResolutionsForCondition(lg, rs, ImpliesPrivate)
+func ResolveSourcePrivacy(lg *LicenseGraph) ResolutionSet {
+	ResolveTopDownConditions(lg)
+	return WalkResolutionsForCondition(lg, ImpliesPrivate)
 }
diff --git a/tools/compliance/policy/resolveprivacy_test.go b/tools/compliance/policy/resolveprivacy_test.go
index 25772bb..2072d22 100644
--- a/tools/compliance/policy/resolveprivacy_test.go
+++ b/tools/compliance/policy/resolveprivacy_test.go
@@ -81,7 +81,7 @@
 			}
 			expectedRs := toResolutionSet(lg, tt.expectedResolutions)
 			actualRs := ResolveSourcePrivacy(lg)
-			checkSame(actualRs, expectedRs, t)
+			checkResolves(actualRs, expectedRs, t)
 		})
 	}
 }
diff --git a/tools/compliance/policy/resolveshare.go b/tools/compliance/policy/resolveshare.go
index 24efd28..9b6a8bb 100644
--- a/tools/compliance/policy/resolveshare.go
+++ b/tools/compliance/policy/resolveshare.go
@@ -15,7 +15,7 @@
 package compliance
 
 // ResolveSourceSharing implements the policy for source-sharing.
-func ResolveSourceSharing(lg *LicenseGraph) *ResolutionSet {
-	rs := ResolveTopDownConditions(lg)
-	return WalkResolutionsForCondition(lg, rs, ImpliesShared)
+func ResolveSourceSharing(lg *LicenseGraph) ResolutionSet {
+	ResolveTopDownConditions(lg)
+	return WalkResolutionsForCondition(lg, ImpliesShared)
 }
diff --git a/tools/compliance/policy/resolveshare_test.go b/tools/compliance/policy/resolveshare_test.go
index ad3630d..f73888d 100644
--- a/tools/compliance/policy/resolveshare_test.go
+++ b/tools/compliance/policy/resolveshare_test.go
@@ -291,7 +291,7 @@
 			}
 			expectedRs := toResolutionSet(lg, tt.expectedResolutions)
 			actualRs := ResolveSourceSharing(lg)
-			checkSame(actualRs, expectedRs, t)
+			checkResolves(actualRs, expectedRs, t)
 		})
 	}
 }
diff --git a/tools/compliance/policy/shareprivacyconflicts.go b/tools/compliance/policy/shareprivacyconflicts.go
index dabdff5..279e179 100644
--- a/tools/compliance/policy/shareprivacyconflicts.go
+++ b/tools/compliance/policy/shareprivacyconflicts.go
@@ -28,57 +28,37 @@
 
 // Error returns a string describing the conflict.
 func (conflict SourceSharePrivacyConflict) Error() string {
-	return fmt.Sprintf("%s %s from %s and must share from %s %s\n",
-		conflict.SourceNode.name,
-		conflict.PrivacyCondition.name, conflict.PrivacyCondition.origin.name,
-		conflict.ShareCondition.name, conflict.ShareCondition.origin.name)
+	return fmt.Sprintf("%s %s and must share from %s condition\n", conflict.SourceNode.name,
+		conflict.PrivacyCondition.Name(), conflict.ShareCondition.Name())
 }
 
 // IsEqualTo returns true when `conflict` and `other` describe the same conflict.
 func (conflict SourceSharePrivacyConflict) IsEqualTo(other SourceSharePrivacyConflict) bool {
 	return conflict.SourceNode.name == other.SourceNode.name &&
-		conflict.ShareCondition.name == other.ShareCondition.name &&
-		conflict.ShareCondition.origin.name == other.ShareCondition.origin.name &&
-		conflict.PrivacyCondition.name == other.PrivacyCondition.name &&
-		conflict.PrivacyCondition.origin.name == other.PrivacyCondition.origin.name
+		conflict.ShareCondition == other.ShareCondition &&
+		conflict.PrivacyCondition == other.PrivacyCondition
 }
 
 // ConflictingSharedPrivateSource lists all of the targets where conflicting conditions to
 // share the source and to keep the source private apply to the target.
 func ConflictingSharedPrivateSource(lg *LicenseGraph) []SourceSharePrivacyConflict {
-	// shareSource is the set of all source-sharing resolutions.
-	shareSource := ResolveSourceSharing(lg)
-	if shareSource.IsEmpty() {
-		return []SourceSharePrivacyConflict{}
-	}
 
-	// privateSource is the set of all source privacy resolutions.
-	privateSource := ResolveSourcePrivacy(lg)
-	if privateSource.IsEmpty() {
-		return []SourceSharePrivacyConflict{}
-	}
-
+	ResolveTopDownConditions(lg)
 	// combined is the combination of source-sharing and source privacy.
-	combined := JoinResolutionSets(shareSource, privateSource)
+	combined := WalkActionsForCondition(lg, ImpliesShared.Union(ImpliesPrivate))
 
 	// size is the size of the result
 	size := 0
-	for _, actsOn := range combined.ActsOn() {
-		rl := combined.ResolutionsByActsOn(actsOn)
-		size += rl.CountConditionsByName(ImpliesShared) * rl.CountConditionsByName(ImpliesPrivate)
+	for _, cs := range combined {
+		size += cs.Intersection(ImpliesShared).Len() * cs.Intersection(ImpliesPrivate).Len()
 	}
 	if size == 0 {
-		return []SourceSharePrivacyConflict{}
+		return nil
 	}
 	result := make([]SourceSharePrivacyConflict, 0, size)
-	for _, actsOn := range combined.ActsOn() {
-		rl := combined.ResolutionsByActsOn(actsOn)
-		if len(rl) == 0 {
-			continue
-		}
-
-		pconditions := rl.ByName(ImpliesPrivate).AllConditions().AsList()
-		ssconditions := rl.ByName(ImpliesShared).AllConditions().AsList()
+	for actsOn, cs := range combined {
+		pconditions := cs.Intersection(ImpliesPrivate).AsList()
+		ssconditions := cs.Intersection(ImpliesShared).AsList()
 
 		// report all conflicting condition combinations
 		for _, p := range pconditions {
diff --git a/tools/compliance/policy/shareprivacyconflicts_test.go b/tools/compliance/policy/shareprivacyconflicts_test.go
index 162c1fe..ad3f3f4 100644
--- a/tools/compliance/policy/shareprivacyconflicts_test.go
+++ b/tools/compliance/policy/shareprivacyconflicts_test.go
@@ -33,19 +33,13 @@
 // Less returns true when the `i`th element is lexicographically less than
 // the `j`th element.
 func (l byConflict) Less(i, j int) bool {
-	if l[i].SourceNode.name == l[j].SourceNode.name {
-		if l[i].ShareCondition.origin.name == l[j].ShareCondition.origin.name {
-			if l[i].ShareCondition.name == l[j].ShareCondition.name {
-				if l[i].PrivacyCondition.origin.name == l[j].PrivacyCondition.origin.name {
-					return l[i].PrivacyCondition.name < l[j].PrivacyCondition.name
-				}
-				return l[i].PrivacyCondition.origin.name < l[j].PrivacyCondition.origin.name
-			}
-			return l[i].ShareCondition.name < l[j].ShareCondition.name
+	if l[i].SourceNode.Name() == l[j].SourceNode.Name() {
+		if l[i].ShareCondition.Name() == l[j].ShareCondition.Name() {
+			return l[i].PrivacyCondition.Name() < l[j].PrivacyCondition.Name()
 		}
-		return l[i].ShareCondition.origin.name < l[j].ShareCondition.origin.name
+		return l[i].ShareCondition.Name() < l[j].ShareCondition.Name()
 	}
-	return l[i].SourceNode.name < l[j].SourceNode.name
+	return l[i].SourceNode.Name() < l[j].SourceNode.Name()
 }
 
 func TestConflictingSharedPrivateSource(t *testing.T) {
diff --git a/tools/compliance/policy/shipped.go b/tools/compliance/policy/shipped.go
index 6fbbbd6..75c8399 100644
--- a/tools/compliance/policy/shipped.go
+++ b/tools/compliance/policy/shipped.go
@@ -26,12 +26,12 @@
 
 	tset := make(map[*TargetNode]struct{})
 
-	WalkTopDown(lg, func(lg *LicenseGraph, tn *TargetNode, path TargetEdgePath) bool {
+	WalkTopDown(NoEdgeContext{}, lg, func(lg *LicenseGraph, tn *TargetNode, path TargetEdgePath) bool {
 		if _, alreadyWalked := tset[tn]; alreadyWalked {
 			return false
 		}
 		if len(path) > 0 {
-			if !edgeIsDerivation(path[len(path)-1]) {
+			if !edgeIsDerivation(path[len(path)-1].edge) {
 				return false
 			}
 		}
diff --git a/tools/compliance/policy/shipped_test.go b/tools/compliance/policy/shipped_test.go
index 53a8469..718e56f 100644
--- a/tools/compliance/policy/shipped_test.go
+++ b/tools/compliance/policy/shipped_test.go
@@ -17,6 +17,7 @@
 import (
 	"bytes"
 	"sort"
+	"strings"
 	"testing"
 )
 
@@ -110,19 +111,31 @@
 				t.Errorf("unexpected test data error: got %w, want no error", err)
 				return
 			}
+			t.Logf("graph:")
+			for _, edge := range lg.Edges() {
+				t.Logf("  %s", edge.String())
+			}
 			expectedNodes := append([]string{}, tt.expectedNodes...)
-			actualNodes := ShippedNodes(lg).Names()
+			nodeset := ShippedNodes(lg)
+			t.Logf("shipped node set: %s", nodeset.String())
+
+			actualNodes := nodeset.Names()
+			t.Logf("shipped nodes: [%s]", strings.Join(actualNodes, ", "))
+
 			sort.Strings(expectedNodes)
 			sort.Strings(actualNodes)
+
+			t.Logf("sorted nodes: [%s]", strings.Join(actualNodes, ", "))
+			t.Logf("expected nodes: [%s]", strings.Join(expectedNodes, ", "))
                         if len(expectedNodes) != len(actualNodes) {
-				t.Errorf("unexpected number of shipped nodes: got %v with %d nodes, want %v with %d nodes",
-					actualNodes, len(actualNodes), expectedNodes, len(expectedNodes))
+				t.Errorf("unexpected number of shipped nodes: %d nodes, want %d nodes",
+					len(actualNodes), len(expectedNodes))
 				return
 			}
 			for i := 0; i < len(actualNodes); i++ {
 				if expectedNodes[i] != actualNodes[i] {
-					t.Errorf("unexpected node at index %d: got %q in %v, want %q in %v",
-						i, actualNodes[i], actualNodes, expectedNodes[i], expectedNodes)
+					t.Errorf("unexpected node at index %d: got %q, want %q",
+						i, actualNodes[i], expectedNodes[i])
 				}
 			}
 		})
diff --git a/tools/compliance/policy/walk.go b/tools/compliance/policy/walk.go
index 8b6737d..3e73088 100644
--- a/tools/compliance/policy/walk.go
+++ b/tools/compliance/policy/walk.go
@@ -14,27 +14,60 @@
 
 package compliance
 
+// EdgeContextProvider is an interface for injecting edge-specific context
+// into walk paths.
+type EdgeContextProvider interface {
+	// Context returns the context for `edge` when added to `path`.
+	Context(lg *LicenseGraph, path TargetEdgePath, edge *TargetEdge) interface{}
+}
+
+// NoEdgeContext implements EdgeContextProvider for walks that use no context.
+type NoEdgeContext struct{}
+
+// Context returns nil.
+func (ctx NoEdgeContext) Context(lg *LicenseGraph, path TargetEdgePath, edge *TargetEdge) interface{} {
+	return nil
+}
+
+// ApplicableConditionsContext provides the subset of conditions in `universe`
+// that apply to each edge in a path.
+type ApplicableConditionsContext struct {
+	universe LicenseConditionSet
+}
+
+// Context returns the LicenseConditionSet applicable to the edge.
+func (ctx ApplicableConditionsContext) Context(lg *LicenseGraph, path TargetEdgePath, edge *TargetEdge) interface{} {
+	universe := ctx.universe
+	if len(path) > 0 {
+		universe = path[len(path)-1].ctx.(LicenseConditionSet)
+	}
+	return conditionsAttachingAcrossEdge(lg, edge, universe)
+}
+
 // VisitNode is called for each root and for each walked dependency node by
 // WalkTopDown. When VisitNode returns true, WalkTopDown will proceed to walk
 // down the dependences of the node
-type VisitNode func(*LicenseGraph, *TargetNode, TargetEdgePath) bool
+type VisitNode func(lg *LicenseGraph, target *TargetNode, path TargetEdgePath) bool
 
 // WalkTopDown does a top-down walk of `lg` calling `visit` and descending
 // into depenencies when `visit` returns true.
-func WalkTopDown(lg *LicenseGraph, visit VisitNode) {
+func WalkTopDown(ctx EdgeContextProvider, lg *LicenseGraph, visit VisitNode) {
 	path := NewTargetEdgePath(32)
 
-	// must be indexed for fast lookup
-	lg.indexForward()
-
-	var walk func(f string)
-	walk = func(f string) {
-		visitChildren := visit(lg, lg.targets[f], *path)
+	var walk func(fnode *TargetNode)
+	walk = func(fnode *TargetNode) {
+		visitChildren := visit(lg, fnode, *path)
 		if !visitChildren {
 			return
 		}
-		for _, edge := range lg.index[f] {
-			path.Push(TargetEdge{lg, edge})
+		for _, edge := range fnode.edges {
+			var edgeContext interface{}
+			if ctx == nil {
+				edgeContext = nil
+			} else {
+				edgeContext = ctx.Context(lg, *path, edge)
+			}
+			path.Push(edge, edgeContext)
 			walk(edge.dependency)
 			path.Pop()
 		}
@@ -42,35 +75,164 @@
 
 	for _, r := range lg.rootFiles {
 		path.Clear()
-		walk(r)
+		walk(lg.targets[r])
 	}
 }
 
+// resolutionKey identifies results from walking a specific target for a
+// specific set of conditions.
+type resolutionKey struct {
+	target *TargetNode
+	cs LicenseConditionSet
+}
+
 // WalkResolutionsForCondition performs a top-down walk of the LicenseGraph
-// resolving all distributed works for condition `names`.
-func WalkResolutionsForCondition(lg *LicenseGraph, rs *ResolutionSet, names ConditionNames) *ResolutionSet {
+// resolving all distributed works for `conditions`.
+func WalkResolutionsForCondition(lg *LicenseGraph, conditions LicenseConditionSet) ResolutionSet {
 	shipped := ShippedNodes(lg)
 
 	// rmap maps 'attachesTo' targets to the `actsOn` targets and applicable conditions
-	//
-	// rmap is the resulting ResolutionSet
-	rmap := make(map[*TargetNode]actionSet)
+	rmap := make(map[resolutionKey]ActionSet)
 
-	WalkTopDown(lg, func(lg *LicenseGraph, tn *TargetNode, _ TargetEdgePath) bool {
-		if _, ok := rmap[tn]; ok {
+	// cmap identifies previously walked target/condition pairs.
+	cmap := make(map[resolutionKey]struct{})
+
+	// result accumulates the resolutions to return.
+	result := make(ResolutionSet)
+	WalkTopDown(ApplicableConditionsContext{conditions}, lg, func(lg *LicenseGraph, tn *TargetNode, path TargetEdgePath) bool {
+		universe := conditions
+		if len(path) > 0 {
+			universe = path[len(path)-1].ctx.(LicenseConditionSet)
+		}
+
+		if universe.IsEmpty() {
+			return false
+		}
+		key := resolutionKey{tn, universe}
+
+		if _, alreadyWalked := cmap[key]; alreadyWalked {
+			pure := true
+			for _, p := range path {
+				target := p.Target()
+				tkey := resolutionKey{target, universe}
+				if _, ok := rmap[tkey]; !ok {
+					rmap[tkey] = make(ActionSet)
+				}
+				// attach prior walk outcome to ancestor
+				for actsOn, cs := range rmap[key] {
+					rmap[tkey][actsOn] = cs
+				}
+				// if prior walk produced results, copy results
+				// to ancestor.
+				if _, ok := result[tn]; ok && pure {
+					if _, ok := result[target]; !ok {
+						result[target] = make(ActionSet)
+					}
+					for actsOn, cs := range result[tn] {
+						result[target][actsOn] = cs
+					}
+					pure = target.IsContainer()
+				}
+			}
+			// if all ancestors are pure aggregates, attach
+			// matching prior walk conditions to self. Prior walk
+			// will not have done so if any ancestor was not an
+			// aggregate.
+			if pure {
+				match := rmap[key][tn].Intersection(universe)
+				if !match.IsEmpty() {
+					if _, ok := result[tn]; !ok {
+						result[tn] = make(ActionSet)
+					}
+					result[tn][tn] = match
+				}
+			}
+			return false
+		}
+		// no need to walk node or dependencies if not shipped
+		if !shipped.Contains(tn) {
+			return false
+		}
+		if _, ok := rmap[key]; !ok {
+			rmap[key] = make(ActionSet)
+		}
+		// add self to walk outcome
+		rmap[key][tn] = tn.resolution
+		cmap[key] = struct{}{}
+		cs := tn.resolution
+		if !cs.IsEmpty() {
+			cs = cs.Intersection(universe)
+			pure := true
+			for _, p := range path {
+				target := p.Target()
+				tkey := resolutionKey{target, universe}
+				if _, ok := rmap[tkey]; !ok {
+					rmap[tkey] = make(ActionSet)
+				}
+				// copy current node's action into ancestor
+				rmap[tkey][tn] = tn.resolution
+				// conditionally put matching conditions into
+				// result
+				if pure && !cs.IsEmpty() {
+					if _, ok := result[target]; !ok {
+						result[target] = make(ActionSet)
+					}
+					result[target][tn] = cs
+					pure = target.IsContainer()
+				}
+			}
+			// if all ancestors are pure aggregates, attach
+			// matching conditions to self.
+			if pure && !cs.IsEmpty() {
+				if _, ok := result[tn]; !ok {
+					result[tn] = make(ActionSet)
+				}
+				result[tn][tn] = cs
+			}
+		}
+		return true
+	})
+
+	return result
+}
+
+// WalkActionsForCondition performs a top-down walk of the LicenseGraph
+// resolving all distributed works for `conditions`.
+func WalkActionsForCondition(lg *LicenseGraph, conditions LicenseConditionSet) ActionSet {
+	shipped := ShippedNodes(lg)
+
+	// cmap identifies previously walked target/condition pairs.
+	cmap := make(map[resolutionKey]struct{})
+
+	// amap maps 'actsOn' targets to the applicable conditions
+	//
+	// amap is the resulting ActionSet
+	amap := make(ActionSet)
+	WalkTopDown(ApplicableConditionsContext{conditions}, lg, func(lg *LicenseGraph, tn *TargetNode, path TargetEdgePath) bool {
+		universe := conditions
+		if len(path) > 0 {
+			universe = path[len(path)-1].ctx.(LicenseConditionSet)
+		}
+		if universe.IsEmpty() {
+			return false
+		}
+		key := resolutionKey{tn, universe}
+		if _, ok := cmap[key]; ok {
 			return false
 		}
 		if !shipped.Contains(tn) {
 			return false
 		}
-		if as, ok := rs.resolutions[tn]; ok {
-			fas := as.byActsOn(shipped).byName(names)
-			if !fas.isEmpty() {
-				rmap[tn] = fas
+		cs := universe.Intersection(tn.resolution)
+		if !cs.IsEmpty() {
+			if _, ok := amap[tn]; ok {
+				amap[tn] = cs
+			} else {
+				amap[tn] = amap[tn].Union(cs)
 			}
 		}
-		return tn.IsContainer() // descend into containers
+		return true
 	})
 
-	return &ResolutionSet{rmap}
+	return amap
 }
diff --git a/tools/compliance/policy/walk_test.go b/tools/compliance/policy/walk_test.go
index 07710aa..a2ec6e7 100644
--- a/tools/compliance/policy/walk_test.go
+++ b/tools/compliance/policy/walk_test.go
@@ -22,7 +22,7 @@
 func TestWalkResolutionsForCondition(t *testing.T) {
 	tests := []struct {
 		name                string
-		condition           ConditionNames
+		condition           LicenseConditionSet
 		roots               []string
 		edges               []annotated
 		expectedResolutions []res
@@ -624,8 +624,617 @@
 				return
 			}
 			expectedRs := toResolutionSet(lg, tt.expectedResolutions)
-			actualRs := WalkResolutionsForCondition(lg, ResolveTopDownConditions(lg), tt.condition)
-			checkSame(actualRs, expectedRs, t)
+			ResolveTopDownConditions(lg)
+			actualRs := WalkResolutionsForCondition(lg, tt.condition)
+			checkResolves(actualRs, expectedRs, t)
+		})
+	}
+}
+
+func TestWalkActionsForCondition(t *testing.T) {
+	tests := []struct {
+		name            string
+		condition       LicenseConditionSet
+		roots           []string
+		edges           []annotated
+		expectedActions []act
+	}{
+		{
+			name:      "firstparty",
+			condition: ImpliesNotice,
+			roots:     []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:      "notice",
+			condition: ImpliesNotice,
+			roots:     []string{"mitBin.meta_lic"},
+			edges: []annotated{
+				{"mitBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"mitBin.meta_lic", "mitBin.meta_lic", "notice"},
+				{"mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:      "fponlgplnotice",
+			condition: ImpliesNotice,
+			roots:     []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "fponlgpldynamicnotice",
+			condition: ImpliesNotice,
+			roots:     []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedActions: []act{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+			},
+		},
+		{
+			name:      "independentmodulenotice",
+			condition: ImpliesNotice,
+			roots:     []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
+			},
+			expectedActions: []act{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+			},
+		},
+		{
+			name:      "independentmodulerestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
+			},
+			expectedActions: []act{},
+		},
+		{
+			name:      "independentmodulestaticnotice",
+			condition: ImpliesNotice,
+			roots:     []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "independentmodulestaticrestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "dependentmodulenotice",
+			condition: ImpliesNotice,
+			roots:     []string{"dependentModule.meta_lic"},
+			edges: []annotated{
+				{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
+			},
+			expectedActions: []act{
+				{"dependentModule.meta_lic", "dependentModule.meta_lic", "notice"},
+				{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "dependentmodulerestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"dependentModule.meta_lic"},
+			edges: []annotated{
+				{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
+			},
+			expectedActions: []act{
+				{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "lgplonfpnotice",
+			condition: ImpliesNotice,
+			roots:     []string{"lgplBin.meta_lic"},
+			edges: []annotated{
+				{"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"lgplBin.meta_lic", "lgplBin.meta_lic", "restricted"},
+				{"apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+				{"apacheLib.meta_lic", "lgplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "lgplonfprestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"lgplBin.meta_lic"},
+			edges: []annotated{
+				{"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"lgplBin.meta_lic", "lgplBin.meta_lic", "restricted"},
+				{"apacheLib.meta_lic", "lgplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "lgplonfpdynamicnotice",
+			condition: ImpliesNotice,
+			roots:     []string{"lgplBin.meta_lic"},
+			edges: []annotated{
+				{"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedActions: []act{
+				{"lgplBin.meta_lic", "lgplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "lgplonfpdynamicrestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"lgplBin.meta_lic"},
+			edges: []annotated{
+				{"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedActions: []act{
+				{"lgplBin.meta_lic", "lgplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "gplonfpnotice",
+			condition: ImpliesNotice,
+			roots:     []string{"gplBin.meta_lic"},
+			edges: []annotated{
+				{"gplBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"gplBin.meta_lic", "gplBin.meta_lic", "restricted"},
+				{"apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+				{"apacheLib.meta_lic", "gplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "gplonfprestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"gplBin.meta_lic"},
+			edges: []annotated{
+				{"gplBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"gplBin.meta_lic", "gplBin.meta_lic", "restricted"},
+				{"apacheLib.meta_lic", "gplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "gplcontainernotice",
+			condition: ImpliesNotice,
+			roots:     []string{"gplContainer.meta_lic"},
+			edges: []annotated{
+				{"gplContainer.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"gplContainer.meta_lic", "gplContainer.meta_lic", "restricted"},
+				{"apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+				{"apacheLib.meta_lic", "gplContainer.meta_lic", "restricted"},
+				{"apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+				{"apacheLib.meta_lic", "gplContainer.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "gplcontainerrestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"gplContainer.meta_lic"},
+			edges: []annotated{
+				{"gplContainer.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"gplContainer.meta_lic", "gplContainer.meta_lic", "restricted"},
+				{"apacheLib.meta_lic", "gplContainer.meta_lic", "restricted"},
+				{"apacheLib.meta_lic", "gplContainer.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "gploncontainernotice",
+			condition: ImpliesNotice,
+			roots:     []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+				{"apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+				{"gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "gploncontainerrestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "gplonbinnotice",
+			condition: ImpliesNotice,
+			roots:     []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+				{"apacheLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "gplonbinrestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"apacheBin.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "gplonfpdynamicnotice",
+			condition: ImpliesNotice,
+			roots:     []string{"gplBin.meta_lic"},
+			edges: []annotated{
+				{"gplBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedActions: []act{
+				{"gplBin.meta_lic", "gplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "gplonfpdynamicrestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"gplBin.meta_lic"},
+			edges: []annotated{
+				{"gplBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedActions: []act{
+				{"gplBin.meta_lic", "gplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "gplonfpdynamicrestrictedshipped",
+			condition: ImpliesRestricted,
+			roots:     []string{"gplBin.meta_lic", "apacheLib.meta_lic"},
+			edges: []annotated{
+				{"gplBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedActions: []act{
+				{"gplBin.meta_lic", "gplBin.meta_lic", "restricted"},
+				{"apacheLib.meta_lic", "gplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "independentmodulereversenotice",
+			condition: ImpliesNotice,
+			roots:     []string{"gplWithClasspathException.meta_lic"},
+			edges: []annotated{
+				{"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", []string{"dynamic"}},
+			},
+			expectedActions: []act{
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "independentmodulereverserestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"gplWithClasspathException.meta_lic"},
+			edges: []annotated{
+				{"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", []string{"dynamic"}},
+			},
+			expectedActions: []act{
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "independentmodulereverserestrictedshipped",
+			condition: ImpliesRestricted,
+			roots:     []string{"gplWithClasspathException.meta_lic", "apacheBin.meta_lic"},
+			edges: []annotated{
+				{"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", []string{"dynamic"}},
+			},
+			expectedActions: []act{
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "independentmodulereversestaticnotice",
+			condition: ImpliesNotice,
+			roots:     []string{"gplWithClasspathException.meta_lic"},
+			edges: []annotated{
+				{"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "independentmodulereversestaticrestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"gplWithClasspathException.meta_lic"},
+			edges: []annotated{
+				{"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "dependentmodulereversenotice",
+			condition: ImpliesNotice,
+			roots:     []string{"gplWithClasspathException.meta_lic"},
+			edges: []annotated{
+				{"gplWithClasspathException.meta_lic", "dependentModule.meta_lic", []string{"dynamic"}},
+			},
+			expectedActions: []act{
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "dependentmodulereverserestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"gplWithClasspathException.meta_lic"},
+			edges: []annotated{
+				{"gplWithClasspathException.meta_lic", "dependentModule.meta_lic", []string{"dynamic"}},
+			},
+			expectedActions: []act{
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "dependentmodulereverserestrictedshipped",
+			condition: ImpliesRestricted,
+			roots:     []string{"gplWithClasspathException.meta_lic", "dependentModule.meta_lic"},
+			edges: []annotated{
+				{"gplWithClasspathException.meta_lic", "dependentModule.meta_lic", []string{"dynamic"}},
+			},
+			expectedActions: []act{
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "ponrnotice",
+			condition: ImpliesNotice,
+			roots:     []string{"proprietary.meta_lic"},
+			edges: []annotated{
+				{"proprietary.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"proprietary.meta_lic", "proprietary.meta_lic", "proprietary"},
+				{"proprietary.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "ponrrestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"proprietary.meta_lic"},
+			edges: []annotated{
+				{"proprietary.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"proprietary.meta_lic", "gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "ponrproprietary",
+			condition: ImpliesProprietary,
+			roots:     []string{"proprietary.meta_lic"},
+			edges: []annotated{
+				{"proprietary.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"proprietary.meta_lic", "proprietary.meta_lic", "proprietary"},
+			},
+		},
+		{
+			name:      "ronpnotice",
+			condition: ImpliesNotice,
+			roots:     []string{"gplBin.meta_lic"},
+			edges: []annotated{
+				{"gplBin.meta_lic", "proprietary.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"gplBin.meta_lic", "gplBin.meta_lic", "restricted"},
+				{"proprietary.meta_lic", "proprietary.meta_lic", "proprietary"},
+				{"proprietary.meta_lic", "gplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "ronprestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"gplBin.meta_lic"},
+			edges: []annotated{
+				{"gplBin.meta_lic", "proprietary.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"gplBin.meta_lic", "gplBin.meta_lic", "restricted"},
+				{"proprietary.meta_lic", "gplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "ronpproprietary",
+			condition: ImpliesProprietary,
+			roots:     []string{"gplBin.meta_lic"},
+			edges: []annotated{
+				{"gplBin.meta_lic", "proprietary.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"proprietary.meta_lic", "proprietary.meta_lic", "proprietary"},
+			},
+		},
+		{
+			name:      "noticeonb_e_onotice",
+			condition: ImpliesNotice,
+			roots:     []string{"mitBin.meta_lic"},
+			edges: []annotated{
+				{"mitBin.meta_lic", "by_exception.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"mitBin.meta_lic", "mitBin.meta_lic", "notice"},
+				{"by_exception.meta_lic", "by_exception.meta_lic", "by_exception_only"},
+			},
+		},
+		{
+			name:      "noticeonb_e_orestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"mitBin.meta_lic"},
+			edges: []annotated{
+				{"mitBin.meta_lic", "by_exception.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{},
+		},
+		{
+			name:      "noticeonb_e_ob_e_o",
+			condition: ImpliesByExceptionOnly,
+			roots:     []string{"mitBin.meta_lic"},
+			edges: []annotated{
+				{"mitBin.meta_lic", "by_exception.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"by_exception.meta_lic", "by_exception.meta_lic", "by_exception_only"},
+			},
+		},
+		{
+			name:      "b_e_oonnoticenotice",
+			condition: ImpliesNotice,
+			roots:     []string{"by_exception.meta_lic"},
+			edges: []annotated{
+				{"by_exception.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"by_exception.meta_lic", "by_exception.meta_lic", "by_exception_only"},
+				{"mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:      "b_e_oonnoticerestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"by_exception.meta_lic"},
+			edges: []annotated{
+				{"by_exception.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{},
+		},
+		{
+			name:      "b_e_oonnoticeb_e_o",
+			condition: ImpliesByExceptionOnly,
+			roots:     []string{"by_exception.meta_lic"},
+			edges: []annotated{
+				{"by_exception.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"by_exception.meta_lic", "by_exception.meta_lic", "by_exception_only"},
+			},
+		},
+		{
+			name:      "noticeonrecipnotice",
+			condition: ImpliesNotice,
+			roots:     []string{"mitBin.meta_lic"},
+			edges: []annotated{
+				{"mitBin.meta_lic", "mplLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"mitBin.meta_lic", "mitBin.meta_lic", "notice"},
+				{"mplLib.meta_lic", "mplLib.meta_lic", "reciprocal"},
+			},
+		},
+		{
+			name:      "noticeonreciprecip",
+			condition: ImpliesReciprocal,
+			roots:     []string{"mitBin.meta_lic"},
+			edges: []annotated{
+				{"mitBin.meta_lic", "mplLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"mplLib.meta_lic", "mplLib.meta_lic", "reciprocal"},
+			},
+		},
+		{
+			name:      "reciponnoticenotice",
+			condition: ImpliesNotice,
+			roots:     []string{"mplBin.meta_lic"},
+			edges: []annotated{
+				{"mplBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"mplBin.meta_lic", "mplBin.meta_lic", "reciprocal"},
+				{"mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:      "reciponnoticerecip",
+			condition: ImpliesReciprocal,
+			roots:     []string{"mplBin.meta_lic"},
+			edges: []annotated{
+				{"mplBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"mplBin.meta_lic", "mplBin.meta_lic", "reciprocal"},
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			stderr := &bytes.Buffer{}
+			lg, err := toGraph(stderr, tt.roots, tt.edges)
+			if err != nil {
+				t.Errorf("unexpected test data error: got %w, want no error", err)
+				return
+			}
+			expectedAs := toActionSet(lg, tt.expectedActions)
+			ResolveTopDownConditions(lg)
+			actualAs := WalkActionsForCondition(lg, tt.condition)
+			checkResolvesActions(lg, actualAs, expectedAs, t)
 		})
 	}
 }