Repository: oscal-compass/compliance-trestle
Pull request #1519 (2024-02-22 14:17:46)

chore: Trestle release
## Types of changes

<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->

- [ ] Hot fix (emergency fix and release)
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Documentation (change which affects the documentation site)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
- [x] Release (`develop` -> `main`)

## Quality assurance (all should be covered).

- [x] My code follows the code style of this project.
- [x] Documentation for my change is up to date?
- [x] My PR meets testing requirements.
- [x] All new and existing tests passed.
- [x] All commits are signed-off.

## Summary

Bumping from version 2.5.1 to 2.6.0

## Key links:

- [Sonar coverage](https://sonarcloud.io/dashboard?id=compliance-trestle)

# Before you merge

- Ensure it is a 'squash commit' if not a release.
- Ensure CI is currently passing
- Check sonar. If you are working for a fork a maintainer will reach out, if required.



Patch:
diff --git a/README.md b/README.md
index eb9ab2de..415cda4b 100644
--- a/README.md
+++ b/README.md
@@ -106,38 +106,35 @@ A collection of demos utilizing trestle can be found in the related project [com
 
 Compliance trestle is currently stable and is based on NIST OSCAL version 1.0.4, with active development continuing.
 
-## Community call
+## Community meetings and communications
 
-We would like to share development in progress for compliance trestle, coming soon and get feedback from community on what features would they like to see in compliance trestle.\
-The community call will happen every 2 week(s) on Tuesday at 10.00am EST.\
-Meeting information:
+##### Scheduled meetings
 
-```
-Compliance Trestle Community Call
-
-Join from the meeting link
-https://ibm.webex.com/ibm/j.php?MTID=m030fdef5ac2d09f46f04813bb5e9dc6b
-Tuesday, September 5, 2023 10:00 AM | 30 minutes | (UTC-04:00) Eastern Time (US & Canada)
-Occurs every 2 week(s) on Tuesday effective 9/5/2023 from 10:00 AM to 10:30 AM, (UTC-04:00) Eastern Time (US & Canada)
- 
-Join by meeting number
-Meeting number (access code): 146 967 4515
- 
-Tap to join from a mobile device (attendees only)
-1-844-531-0958,,1469674515#43533276# United States Toll Free
-+1-669-234-1178,,1469674515#43533276# United States Toll
-Some mobile devices may ask attendees to enter a numeric password.
- 
-Join by phone
-1-844-531-0958 United States Toll Free
-1-669-234-1178 United States Toll
-Global call-in numbers  |  Toll-free calling restrictions
- 
-Join from a video system or application
-Dial 1469674515@ibm.webex.com
-You can also dial 173.243.2.68 and enter your meeting number.
+Please attend! All are invited.
 
-```
+**When**: Every other Tuesday at 10:00 ET [convert to your local time](https://dateful.com/convert/est-edt-eastern-time)
+
+To discover the actual meeting dates:
+
+- Go to [Google Calendar](https://calendar.google.com/calendar/u/0/embed?src=0b8u5el8ta4s93t2cm72tuvhhk@group.calendar.google.com&ctz=America/Los_Angeles)
+- Look at entries in `Tue` day of week for *Compliance Trestle Community Call*
+- To add to your calendar, `click` on `Compliance Trestle Community Call` and choose `copy to my calendar`
+
+**Where**: [https://zoom.us/j/92729235315](https://zoom.us/j/92729235315)
+
+- Meeting Id: 927 2923 5315
+
+- Passcode: 233140
+
+- **Note**: Use the passcode above to login to Zoom (or you can login to Zoom using another account like Google, Facebook)
+
+**What**: Meeting agenda and notes [Google Docs](https://docs.google.com/document/d/1z9xvt-Z97j4CtEH1-nR9sMWul7jQkUi_fNY7BdMPgxM/edit#heading=h.nohkp1kbeduj)
+
+##### Chat anytime
+
+Slack: [# compliance-grc](https://cloud-native.slack.com/archives/C066TMUBEL8)
+
+- **Note**: You can login to Slack using another account like Google, Apple
 
 ## Contributing to Trestle
 
diff --git a/setup.cfg b/setup.cfg
index 9118fa3f..72d7a001 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -28,7 +28,7 @@ include_package_data = True
 install_requires =
     attrs
     ilcli
-    cryptography==41.0.6
+    cryptography==42.0.0
     paramiko==3.4.0
     ruamel.yaml
     furl
diff --git a/trestle/core/catalog/catalog_interface.py b/trestle/core/catalog/catalog_interface.py
index 347fd5df..2a901f1c 100644
--- a/trestle/core/catalog/catalog_interface.py
+++ b/trestle/core/catalog/catalog_interface.py
@@ -298,16 +298,20 @@ class CatalogInterface():
         id_map = {}
         for control in self.get_all_controls_from_catalog(True):
             statement_part = get_item_from_list(control.parts, const.STATEMENT, lambda p: p.name)
-            if statement_part:
-                id_dict: Dict[str, str] = {}
-                for sub_part in as_list(statement_part.parts):
-                    label = ControlInterface.get_label(sub_part)
-                    if label_as_key:
-                        id_dict[label] = sub_part.id
-                    else:
-                        id_dict[sub_part.id] = label
-                if id_dict:
-                    id_map[control.id] = id_dict
+            if not statement_part:
+                continue
+            id_dict: Dict[str, str] = {}
+            for sub_part in as_list(statement_part.parts):
+                label = ControlInterface.get_label(sub_part)
+                # skip add to map for empty label
+                if not label:
+                    continue
+                if label_as_key:
+                    id_dict[label] = sub_part.id
+                else:
+                    id_dict[sub_part.id] = label
+            if id_dict:
+                id_map[control.id] = id_dict
         return id_map
 
     @staticmethod
@@ -748,16 +752,17 @@ class CatalogInterface():
             rules_params = {}
             rules_param_names = []
             for comp_name, rules_params_dict in as_dict(context.rules_params_dict).items():
-                for rule_id, rules_param in rules_params_dict.items():
+                for rule_id, rules_parameters in rules_params_dict.items():
                     if rule_id in rule_ids.get(comp_name, []):
-                        param_name = rules_param['name']
-                        rules_param_names.append(param_name)
-                        rules_param[const.HEADER_RULE_ID] = rule_id_rule_name_map[comp_name].get(rule_id, None)
-                        deep_append(rules_params, [comp_name], rules_param)
-                        deep_set(
-                            param_id_rule_name_map, [comp_name, rules_param['name']],
-                            rule_id_rule_name_map[comp_name][rule_id]
-                        )
+                        for rule_parameter in rules_parameters:
+                            param_name = rule_parameter['name']
+                            rules_param_names.append(param_name)
+                            rule_parameter[const.HEADER_RULE_ID] = rule_id_rule_name_map[comp_name].get(rule_id, None)
+                            deep_append(rules_params, [comp_name], rule_parameter)
+                            deep_set(
+                                param_id_rule_name_map, [comp_name, rule_parameter['name']],
+                                rule_id_rule_name_map[comp_name][rule_id]
+                            )
             set_or_pop(header, const.RULES_PARAMS_TAG, rules_params)
 
             self._extend_rules_param_list(control_id, header, param_id_rule_name_map)
@@ -869,12 +874,15 @@ class CatalogInterface():
         """
         context.rules_dict = {}
         context.rules_params_dict = {}
+        comps_uuids = []
         for comp_def_name in context.comp_def_name_list:
             context.comp_def, _ = ModelUtils.load_model_for_class(
                 context.trestle_root,
                 comp_def_name,
                 comp.ComponentDefinition
             )
+            component_uuids = [comp.uuid for comp in context.comp_def.components]
+            comps_uuids.extend(component_uuids)
             for component in as_list(context.comp_def.components):
                 context.component = component
                 context.comp_name = component.title
@@ -887,6 +895,14 @@ class CatalogInterface():
                     self._add_control_imp_comp_info(context, part_id_map, comp_rules_props)
                 # add the rule_id to the param_dict
                 for param_comp_name, rule_param_dict in context.rules_params_dict.items():
-                    for rule_tag, param_dict in rule_param_dict.items():
-                        rule_dict = deep_get(context.rules_dict, [param_comp_name, rule_tag], {})
-                        param_dict[const.HEADER_RULE_ID] = rule_dict.get(const.NAME, 'unknown_rule')
+                    for rule_tag, params_list in rule_param_dict.items():
+                        for param in params_list:
+                            rule_dict = deep_get(context.rules_dict, [param_comp_name, rule_tag], {})
+                            param[const.HEADER_RULE_ID] = rule_dict.get(const.NAME, 'unknown_rule')
+        # determine if there are duplicated uuids and throw an exception
+        dup_comp_uuids = set({comp_uuid for comp_uuid in comps_uuids if comps_uuids.count(comp_uuid) > 1})
+        if len(dup_comp_uuids) > 0:
+            # throw an exception if there are repeated component uuids
+            for comp_uuid in dup_comp_uuids:
+                logger.error(f'Component uuid { comp_uuid } is duplicated')
+            raise TrestleError('Component uuids cannot be duplicated between different component definitions')
diff --git a/trestle/core/catalog/catalog_writer.py b/trestle/core/catalog/catalog_writer.py
index b422e743..4f706533 100644
--- a/trestle/core/catalog/catalog_writer.py
+++ b/trestle/core/catalog/catalog_writer.py
@@ -235,9 +235,10 @@ class CatalogWriter():
         """Merge info in the rules params dict and the rules param vals dict."""
         for comp_name, comp_dict in context.rules_params_dict.items():
             rules_dict = context.rules_dict.get(comp_name, {})
-            for rule_id, param_dict in comp_dict.items():
-                rule_name = deep_get(rules_dict, [rule_id, 'name'], 'unknown_rule_name')
-                param_dict[const.HEADER_RULE_ID] = rule_name
+            for rule_id, params_list in comp_dict.items():
+                for param in params_list:
+                    rule_name = deep_get(rules_dict, [rule_id, 'name'], 'unknown_rule_name')
+                    param[const.HEADER_RULE_ID] = rule_name
 
     def write_catalog_as_ssp_markdown(self, context: ControlContext, part_id_map: Dict[str, Dict[str, str]]) -> None:
         """
@@ -340,13 +341,13 @@ class CatalogWriter():
         # get top level rule info applying to all controls
         comp_rules_dict, comp_rules_params_dict, _ = ControlInterface.get_rules_and_params_dict_from_item(context.component)  # noqa E501
         context.rules_dict[context.comp_name] = comp_rules_dict
-        context.rules_params_dict.update(comp_rules_params_dict)
+        context.rules_params_dict[context.comp_name] = comp_rules_params_dict
         for control_imp in as_list(context.component.control_implementations):
             control_imp_rules_dict, control_imp_rules_params_dict, _ = ControlInterface.get_rules_and_params_dict_from_item(control_imp)  # noqa E501
             context.rules_dict[context.comp_name].update(control_imp_rules_dict)
             comp_rules_params_dict = context.rules_params_dict.get(context.comp_name, {})
             comp_rules_params_dict.update(control_imp_rules_params_dict)
-            context.rules_params_dict[context.comp_name] = comp_rules_params_dict
+            context.rules_params_dict[context.comp_name].update(comp_rules_params_dict)
             ci_set_params = ControlInterface.get_set_params_from_item(control_imp)
             for imp_req in as_list(control_imp.implemented_requirements):
                 control_part_id_map = part_id_map.get(imp_req.control_id, {})
diff --git a/trestle/core/commands/author/ssp.py b/trestle/core/commands/author/ssp.py
index b8868ee5..065e75c2 100644
--- a/trestle/core/commands/author/ssp.py
+++ b/trestle/core/commands/author/ssp.py
@@ -320,9 +320,11 @@ class SSPAssemble(AuthorCommonCommand):
         # find param_ids needed by rules
         for rule_id in rules_list:
             # get list of param_ids associated with this rule_id
-            param_ids = [param['name'] for param in rule_dict.values() if param['rule-id'] == rule_id]
+            param_ids = [
+                param['name'] for params in rule_dict.values() for param in params if param['rule-id'] == rule_id
+            ]
             needed_param_ids.update(param_ids)
-        all_rule_param_ids = [param['name'] for param in rule_dict.values()]
+        all_rule_param_ids = [param['name'] for params in rule_dict.values() for param in params]
         # any set_param that isn't associated with a rule should be included as a normal control set param with no rule
         for set_param in set_params:
             if set_param.param_id not in all_rule_param_ids:
diff --git a/trestle/core/control_interface.py b/trestle/core/control_interface.py
index 7d363a28..cd7cc162 100644
--- a/trestle/core/control_interface.py
+++ b/trestle/core/control_interface.py
@@ -454,39 +454,50 @@ class ControlInterface:
         """Get all params found in this item with rule_id as key."""
         # id, description, options - where options is a string containing comma-sep list of items
         # params is dict with rule_id as key and value contains: param_name, description and choices
-        params: Dict[str, Dict[str, str]] = {}
+        params: Dict[str, List[Dict[str, str]]] = {}
         props = []
         for prop in as_list(item.props):
-            if prop.name == const.PARAMETER_ID:
+            if const.PARAMETER_ID in prop.name:
                 rule_id = prop.remarks
                 param_name = prop.value
-                if rule_id in params:
-                    raise TrestleError(f'Duplicate param {param_name} found for rule {rule_id}')
-                # create new param for this rule
-                params[rule_id] = {'name': param_name}
+                # rule already exists in parameters dict
+                if rule_id in params.keys():
+                    existing_param = next((prm for prm in params[rule_id] if prm['name'] == param_name), None)
+                    if existing_param is not None:
+                        raise TrestleError(f'Param id for rule {rule_id} already exists')
+                    else:
+                        # append a new parameter for the current rule
+                        params[rule_id].append({'name': param_name})
+                else:
+                    # create new param for this rule for the first parameter
+                    params[rule_id] = [{'name': param_name}]
                 props.append(prop)
-            elif prop.name == const.PARAMETER_DESCRIPTION:
+            elif const.PARAMETER_DESCRIPTION in prop.name:
                 rule_id = prop.remarks
                 if rule_id in params:
-                    params[rule_id]['description'] = prop.value
+                    param = next((prm for prm in params[rule_id] if prm['name'] == param_name), None)
+                    param['description'] = prop.value
                     props.append(prop)
                 else:
                     raise TrestleError(f'Param description for rule {rule_id} found with no param_id')
-            elif prop.name == const.PARAMETER_VALUE_ALTERNATIVES:
+            elif const.PARAMETER_VALUE_ALTERNATIVES in prop.name:
                 rule_id = prop.remarks
                 if rule_id in params:
-                    params[rule_id]['options'] = prop.value
+                    param = next((prm for prm in params[rule_id] if prm['name'] == param_name), None)
+                    param['options'] = prop.value
                     props.append(prop)
                 else:
                     raise TrestleError(f'Param options for rule {rule_id} found with no param_id')
         new_params = {}
-        for rule_id, param in params.items():
-            if 'name' not in param:
-                logger.warning(f'Parameter for rule_id {rule_id} has no matching name.  Ignoring the param.')
-            else:
-                param['description'] = param.get('description', '')
-                param['options'] = param.get('options', '')
-                new_params[rule_id] = param
+        for rule_id, rule_params in params.items():
+            new_params[rule_id] = []
+            for param in rule_params:
+                if 'name' not in param:
+                    logger.warning(f'Parameter for rule_id {rule_id} has no matching name.  Ignoring the param.')
+                else:
+                    param['description'] = param.get('description', '')
+                    param['options'] = param.get('options', '')
+                    new_params[rule_id].append(param)
         return new_params, props
 
     @staticmethod
@@ -1102,14 +1113,16 @@ class ControlInterface:
         """
         for control_imp in as_list(component.control_implementations):
             _, control_imp_param_dict, _ = ControlInterface.get_rules_and_params_dict_from_item(control_imp)
-            control_imp_rule_param_ids = [d['name'] for d in control_imp_param_dict.values()]
+            control_imp_rule_param_ids = [
+                param['name'] for params in control_imp_param_dict.values() for param in params
+            ]
             if profile_title != ModelUtils.get_title_from_model_uri(trestle_root, control_imp.source):
                 continue
             for imp_req in as_list(control_imp.implemented_requirements):
                 if imp_req.control_id != new_imp_req.control_id:
                     continue
                 _, imp_req_param_dict, _ = ControlInterface.get_rules_and_params_dict_from_item(imp_req)
-                imp_req_rule_param_ids = [d['name'] for d in imp_req_param_dict]
+                imp_req_rule_param_ids = [param['name'] for params in imp_req_param_dict.values() for param in params]
                 status = ControlInterface.get_status_from_props(new_imp_req)
                 ControlInterface.insert_status_in_props(imp_req, status)
                 imp_req.description = new_imp_req.description
diff --git a/trestle/tasks/csv_to_oscal_cd.py b/trestle/tasks/csv_to_oscal_cd.py
index 1a0a11c7..da863a64 100644
--- a/trestle/tasks/csv_to_oscal_cd.py
+++ b/trestle/tasks/csv_to_oscal_cd.py
@@ -56,10 +56,11 @@ CHECK_ID = 'Check_Id'
 CHECK_DESCRIPTION = 'Check_Description'
 FETCHER_ID = 'Fetcher_Id'
 FETCHER_DESCRIPTION = 'Fetcher_Description'
-PARAMETER_ID = 'Parameter_Id'
-PARAMETER_DESCRIPTION = 'Parameter_Description'
-PARAMETER_VALUE_DEFAULT = 'Parameter_Value_Default'
-PARAMETER_VALUE_ALTERNATIVES = 'Parameter_Value_Alternatives'
+PARAMETER = 'Parameter'
+PARAMETER_ID = f'{PARAMETER}_Id'
+PARAMETER_DESCRIPTION = f'{PARAMETER}_Description'
+PARAMETER_VALUE_DEFAULT = f'{PARAMETER}_Value_Default'
+PARAMETER_VALUE_ALTERNATIVES = f'{PARAMETER}_Value_Alternatives'
 
 validation = 'validation'
 prefix_rule_set = 'rule_set_'
@@ -183,19 +184,17 @@ class CsvToOscalComponentDefinition(TaskBase):
         logger.info(text1 + text2 + text3)
         text1 = '  required columns:      '
         for text2 in CsvColumn.get_required_column_names():
-            if text2 in ['Rule_Description', 'Profile_Source', 'Profile_Description', 'Control_Id_List']:
+            if text2 in [f'{RULE_DESCRIPTION}', f'{PROFILE_SOURCE}', f'{PROFILE_DESCRIPTION}', f'{CONTROL_ID_LIST}']:
                 text2 += ' (see note 1)'
             logger.info(text1 + '$$' + text2)
             text1 = '                         '
         text1 = '  optional columns:      '
         for text2 in CsvColumn.get_optional_column_names():
-            if text2 in ['Parameter_Id',
-                         'Parameter_Description',
-                         'Parameter_Value_Alternatives',
-                         'Parameter_Value_Default']:
-                text2 += ' (see note 1)'
-            if text2 in ['Check_Id', 'Check_Description']:
-                text2 += ' (see note 2)'
+            text2 += ' (see note 2)'
+            logger.info(text1 + '$' + text2)
+            text1 = '                         '
+        for text2 in CsvColumn.get_parameter_column_names():
+            text2 += ' (see notes 1, 4)'
             logger.info(text1 + '$' + text2)
             text1 = '                         '
         text1 = '  comment columns:       '
@@ -229,6 +228,10 @@ class CsvToOscalComponentDefinition(TaskBase):
         text1 = '       '
         text2 = '[3] column name starting with # causes column to be ignored'
         logger.info(text1 + text2)
+        text1 = '       '
+        text2 = '[4] additional parameters are specified by adding a common suffix per set'
+        text3 = f', for example: {PARAMETER_ID}_1, {PARAMETER_DESCRIPTION}_1, ...{PARAMETER_ID}_2...'
+        logger.info(text1 + text2 + text3)
 
     def configure(self) -> bool:
         """Configure."""
@@ -454,7 +457,7 @@ class CsvToOscalComponentDefinition(TaskBase):
         for prop in component.props:
             if prop.remarks != rule_set:
                 props.append(prop)
-            elif prop.name == PARAMETER_ID:
+            elif prop.name in self._csv_mgr.get_parameter_id_column_names():
                 self._delete_rule_set_parameter(component, prop.value)
             elif prop.name == RULE_ID:
                 self._delete_rule_implemented_requirement(component, prop.value)
@@ -559,11 +562,11 @@ class CsvToOscalComponentDefinition(TaskBase):
                 source = self._csv_mgr.get_value(rule_key, PROFILE_SOURCE)
                 description = self._csv_mgr.get_value(rule_key, PROFILE_DESCRIPTION)
                 control_implementation = self._get_control_implementation(component, source, description)
-                # set-parameter
-                set_parameter = self._create_set_parameter(rule_key)
-                if set_parameter:
+                # set-parameters
+                set_parameters = self._create_set_parameters(rule_key)
+                if set_parameters:
                     control_implementation.set_parameters = as_list(control_implementation.set_parameters)
-                    _OscalHelper.add_set_parameter(control_implementation.set_parameters, set_parameter)
+                    _OscalHelper.add_set_parameters(control_implementation.set_parameters, set_parameters)
                 # control-mappings
                 control_mappings = self._csv_mgr.get_value(rule_key, CONTROL_ID_LIST).split()
                 self._add_rule_prop(control_implementation, control_mappings, rule_key)
@@ -614,7 +617,7 @@ class CsvToOscalComponentDefinition(TaskBase):
             rule_set_mgr.add_prop(prop_name, prop_value, namespace, self.get_class(prop_name))
         if not self._is_validation(rule_key):
             # parameter columns
-            column_names = CsvColumn.get_parameter_column_names()
+            column_names = self._csv_mgr.get_parameter_column_names()
             for column_name in column_names:
                 prop_name = self._get_prop_name(column_name)
                 prop_value = self._csv_mgr.get_value(rule_key, column_name).strip()
@@ -655,23 +658,26 @@ class CsvToOscalComponentDefinition(TaskBase):
             rval.append(value)
         return rval
 
-    def _create_set_parameter(self, rule_key: tuple) -> SetParameter:
-        """Create create set parameters."""
-        set_parameter = None
-        name = self._csv_mgr.get_value(rule_key, PARAMETER_ID)
-        value = self._csv_mgr.get_value(rule_key, PARAMETER_VALUE_DEFAULT)
-        if name and value:
-            values = self._str_to_list(value)
-            set_parameter = SetParameter(
-                param_id=name,
-                values=values,
-            )
-        elif name:
-            row_number = self._csv_mgr.get_row_number(rule_key)
-            column_name = PARAMETER_VALUE_DEFAULT
-            text = f'row "{row_number}" missing value for "{column_name}"'
-            logger.debug(text)
-        return set_parameter
+    def _create_set_parameters(self, rule_key: tuple) -> List[SetParameter]:
+        """Create set parameters."""
+        set_parameters = []
+        for parameter_id_column_name in self._csv_mgr.get_parameter_id_column_names():
+            suffix = parameter_id_column_name.replace(PARAMETER_ID, '')
+            parameter_value_default_column_name = f'{PARAMETER_VALUE_DEFAULT}{suffix}'
+            name = self._csv_mgr.get_value(rule_key, parameter_id_column_name)
+            value = self._csv_mgr.get_value(rule_key, parameter_value_default_column_name)
+            if name and value:
+                values = self._str_to_list(value)
+                set_parameter = SetParameter(
+                    param_id=name,
+                    values=values,
+                )
+                set_parameters.append(set_parameter)
+            elif name:
+                row_number = self._csv_mgr.get_row_number(rule_key)
+                text = f'row "{row_number}" missing value for "{parameter_value_default_column_name}"'
+                logger.debug(text)
+        return set_parameters
 
     def _get_implemented_requirement(
         self, control_implementation: ControlImplementation, control_id: str
@@ -730,7 +736,7 @@ class CsvToOscalComponentDefinition(TaskBase):
             class_ = self.get_class(column_name)
             self._cd_mgr.update_rule_definition(component, rule_set, column_name, column_value, rule_ns, class_)
         # parameter columns
-        column_names = CsvColumn.get_parameter_column_names()
+        column_names = self._csv_mgr.get_parameter_column_names()
         for column_name in column_names:
             column_value = self._csv_mgr.get_value(rule_key, column_name).strip()
             class_ = self.get_class(column_name)
@@ -778,7 +784,7 @@ class CsvToOscalComponentDefinition(TaskBase):
             control_implementation.set_parameters = as_list(control_implementation.set_parameters)
             # add
             rule_key = _CsvMgr.get_rule_key(component_title, component_type, rule_id)
-            values = [self._csv_mgr.get_value(rule_key, PARAMETER_VALUE_DEFAULT)]
+            values = [self._csv_mgr.get_default_value_by_id(rule_key, param_id)]
             set_parameter = SetParameter(
                 param_id=param_id,
                 values=values,
@@ -803,7 +809,7 @@ class CsvToOscalComponentDefinition(TaskBase):
                     if set_parameter.param_id != param_id:
                         continue
                     rule_key = _CsvMgr.get_rule_key(component_title, component_type, rule_id)
-                    values = [self._csv_mgr.get_value(rule_key, PARAMETER_VALUE_DEFAULT)]
+                    values = [self._csv_mgr.get_default_value_by_id(rule_key, param_id)]
                     replacement = SetParameter(
                         param_id=param_id,
                         values=values,
@@ -888,6 +894,12 @@ class CsvToOscalComponentDefinition(TaskBase):
 class _OscalHelper():
     """Oscal Helper."""
 
+    @staticmethod
+    def add_set_parameters(set_parameter_list: List[SetParameter], set_parameter_list_add: List[SetParameter]) -> None:
+        """Add set parameters."""
+        for set_parameter in set_parameter_list_add:
+            _OscalHelper.add_set_parameter(set_parameter_list, set_parameter)
+
     @staticmethod
     def add_set_parameter(set_parameter_list: List[SetParameter], set_parameter: SetParameter) -> None:
         """Add set parameter."""
@@ -983,16 +995,25 @@ class _RuleSetMgr():
     def get_props(self) -> List[Property]:
         """Get props."""
         rval = []
+        # required
         c1 = CsvColumn.get_required_column_names()
         for key in c1:
             if key in self._props.keys():
                 rval.append(self._props[key])
+        # parameter
+        c3 = []
+        for key in self._props.keys():
+            if key.startswith(f'{PARAMETER}'):
+                rval.append(self._props[key])
+                c3.append(key)
+        # optional
         c2 = CsvColumn.get_optional_column_names()
         for key in c2:
             if key in self._props.keys():
                 rval.append(self._props[key])
+        # user
         for key in self._props.keys():
-            if key in c1 or key in c2:
+            if key in c1 or key in c2 or key in c3:
                 continue
             rval.append(self._props[key])
         return rval
@@ -1333,15 +1354,6 @@ class CsvColumn():
         f'{NAMESPACE}',
     ]
 
-    _columns_optional = [
-        f'{PARAMETER_ID}',
-        f'{PARAMETER_DESCRIPTION}',
-        f'{PARAMETER_VALUE_ALTERNATIVES}',
-        f'{PARAMETER_VALUE_DEFAULT}',
-        f'{CHECK_ID}',
-        f'{CHECK_DESCRIPTION}',
-    ]
-
     _columns_required_validation = [
         f'{COMPONENT_TITLE}',
         f'{COMPONENT_DESCRIPTION}',
@@ -1352,15 +1364,46 @@ class CsvColumn():
         f'{CHECK_DESCRIPTION}',
     ]
 
+    _columns_optional = [
+        f'{CHECK_ID}',
+        f'{CHECK_DESCRIPTION}',
+    ]
+
+    _columns_parameter = [
+        f'{PARAMETER_ID}',
+        f'{PARAMETER_DESCRIPTION}',
+        f'{PARAMETER_VALUE_ALTERNATIVES}',
+        f'{PARAMETER_VALUE_DEFAULT}',
+    ]
+
+    _columns_ordered = _columns_required + _columns_parameter + _columns_optional
+
     @staticmethod
     def get_order(column_name: str) -> int:
         """Get order for column_name."""
         rval = sys.maxsize
-        columns_ordered = CsvColumn._columns_required + CsvColumn._columns_optional
-        if column_name in columns_ordered:
-            rval = columns_ordered.index(column_name)
+        if column_name in CsvColumn._columns_ordered:
+            rval = CsvColumn._columns_ordered.index(column_name)
         return rval
 
+    @staticmethod
+    def is_column_name_required(name: str) -> bool:
+        """Is column name required."""
+        return name in (CsvColumn._columns_required + CsvColumn._columns_required_validation)
+
+    @staticmethod
+    def is_column_name_optional(name: str) -> bool:
+        """Is column name optional."""
+        return name in (CsvColumn._columns_optional)
+
+    @staticmethod
+    def is_column_name_parameter(name: str) -> bool:
+        """Is column name parameter."""
+        for cname in CsvColumn._columns_parameter:
+            if name.startswith(cname):
+                return True
+        return False
+
     @staticmethod
     def get_required_column_names() -> List[str]:
         """Get required column names."""
@@ -1376,11 +1419,10 @@ class CsvColumn():
         return rval
 
     @staticmethod
-    def get_reserved_column_names() -> List[str]:
-        """Get reserved column names."""
+    def get_parameter_column_names() -> List[str]:
+        """Get parameter column names."""
         rval = []
-        rval += CsvColumn._columns_required
-        rval += CsvColumn._columns_optional
+        rval += CsvColumn._columns_parameters
         return rval
 
     @staticmethod
@@ -1417,12 +1459,7 @@ class CsvColumn():
     ]
 
     # optional columns which do not become properties, initially
-    _columns_optional_filtered = [
-        f'{PARAMETER_ID}',
-        f'{PARAMETER_DESCRIPTION}',
-        f'{PARAMETER_VALUE_ALTERNATIVES}',
-        f'{PARAMETER_VALUE_DEFAULT}',
-    ]
+    _columns_optional_filtered = []
 
     _columns_filtered = _columns_required_filtered + _columns_optional_filtered
 
@@ -1462,13 +1499,6 @@ class CsvColumn():
         f'{PARAMETER_VALUE_ALTERNATIVES}',
     ]
 
-    @staticmethod
-    def get_parameter_column_names() -> List[str]:
-        """Get parameter column names."""
-        rval = []
-        rval += CsvColumn._columns_parameters
-        return rval
-
     # optional columns which require Param_Id be present in the row
     _columns_parameters_dependent = [
         f'{PARAMETER_DESCRIPTION}',
@@ -1477,6 +1507,9 @@ class CsvColumn():
     ]
 
 
+Row = Iterator[List[str]]
+
+
 class _CsvMgr():
     """Csv Manager."""
 
@@ -1511,32 +1544,64 @@ class _CsvMgr():
             if source and source not in self._csv_profile_list:
                 self._csv_profile_list.append(source)
             description = self.get_row_value(row, PROFILE_DESCRIPTION)
-            param_id = self.get_row_value(row, PARAMETER_ID)
-            if param_id:
-                key = (component_title, component_type, rule_id, source, description, param_id)
-                self._csv_set_params_map[key] = [row_num, row]
-                logger.debug(f'csv-set-parameters: {key} {self._csv_set_params_map[key][0]}')
+            for param_id_key in self.get_parameter_id_column_names():
+                param_id = self.get_row_value(row, param_id_key)
+                if param_id:
+                    key = (component_title, component_type, rule_id, source, description, param_id)
+                    self._csv_set_params_map[key] = [row_num, row]
+                    logger.debug(f'csv-set-parameters: {key} {self._csv_set_params_map[key][0]}')
             # control mappings
-            control_mappings = self.get_row_value(row, CONTROL_ID_LIST)
-            if control_mappings:
-                controls = control_mappings.split()
-                for control in controls:
-                    key = (component_description, component_type, rule_id, source, description, control)
-                    self._csv_controls_map[key] = [row_num, row]
+            self._control_mappings(row_num, row, component_description, component_type, rule_id, source, description)
         logger.debug(f'csv rules: {len(self._csv_rules_map)}')
         logger.debug(f'csv params: {len(self._csv_set_params_map)}')
         logger.debug(f'csv controls: {len(self._csv_controls_map)}')
 
+    def _control_mappings(
+        self,
+        row_num: int,
+        row: Row,
+        component_description: str,
+        component_type: str,
+        rule_id: str,
+        source: str,
+        description: str
+    ) -> None:
+        """Control_mappings."""
+        control_mappings = self.get_row_value(row, CONTROL_ID_LIST)
+        if control_mappings:
+            controls = control_mappings.split()
+            for control in controls:
+                key = (component_description, component_type, rule_id, source, description, control)
+                self._csv_controls_map[key] = [row_num, row]
+
     @staticmethod
     def get_rule_key(component_title: str, component_type: str, rule_id: str) -> tuple:
         """Get rule_key."""
         return (component_title, component_type, rule_id)
 
+    def get_parameter_id_column_names(self) -> List[str]:
+        """Get parameter_id column_names."""
+        col_names = []
+        for col_name in self._csv[0]:
+            if col_name.startswith(PARAMETER_ID):
+                col_names.append(col_name)
+        return col_names
+
+    def get_parameter_column_names(self) -> List[str]:
+        """Get parameter column_names."""
+        col_names = []
+        for col_name in self._csv[0]:
+            if col_name.startswith(PARAMETER_VALUE_DEFAULT):
+                continue
+            if col_name.startswith(PARAMETER):
+                col_names.append(col_name)
+        return col_names
+
     def get_profile_list(self):
         """Get profile list."""
         return [] + self._csv_profile_list
 
-    def row_generator(self) -> Generator[Union[int, Iterator[List[str]]], None, None]:
+    def row_generator(self) -> Generator[Union[int, Row], None, None]:
         """Generate rows."""
         index = 0
         for row in self._csv:
@@ -1652,11 +1717,25 @@ class _CsvMgr():
         row = self.get_row(rule_key)
         return self.get_row_value(row, name)
 
+    def get_default_value_by_id(self, rule_key: tuple, name: str) -> str:
+        """Get default value for specified parameter id."""
+        rval = None
+        for parameter_id_column_name in self.get_parameter_id_column_names():
+            parameter_id_name = self.get_value(rule_key, parameter_id_column_name)
+            if name == parameter_id_name:
+                suffix = parameter_id_column_name.replace(PARAMETER_ID, '')
+                parameter_value_default_column_name = f'{PARAMETER_VALUE_DEFAULT}{suffix}'
+                rval = self.get_value(rule_key, parameter_value_default_column_name)
+                break
+        return rval
+
     def get_user_column_names(self) -> List[str]:
         """Get user column names."""
         user_column_names = []
-        reserved_column_names = CsvColumn.get_reserved_column_names()
         for column_name in self._csv[0]:
-            if column_name not in reserved_column_names:
+            t1 = CsvColumn.is_column_name_required(column_name)
+            t2 = CsvColumn.is_column_name_optional(column_name)
+            t3 = CsvColumn.is_column_name_parameter(column_name)
+            if not (t1 or t2 or t3):
                 user_column_names.append(column_name)
         return user_column_names


Test patch:
diff --git a/tests/data/csv/bp.sample.v4.csv b/tests/data/csv/bp.sample.v4.csv
new file mode 100644
index 00000000..ec11adf5
--- /dev/null
+++ b/tests/data/csv/bp.sample.v4.csv
@@ -0,0 +1,12 @@
+Reference_Id,Rule_Id,Rule_Description,Check_Id,Check_Description,Fetcher,Fetcher_Description,Profile_Source,Profile_Description,Component_Type,Control_Id_List,Component_Title,Component_Description,Parameter_Id,Parameter_Description,Parameter_Value_Default,Parameter_Value_Alternatives,Parameter_Id2,Parameter_Description2,Parameter_Value_Default2,Parameter_Value_Alternatives2,Namespace
+column description,column description,column description,,,,,,,,,,,,,,,,,,,
+3000020,account_owner_authorized_ip_range_configured,Ensure authorized IP ranges are configured by the account owner,account_owner_authorized_ip_range_configured,Check whether authorized IP ranges are configured by the account owner,,,https://github.com/usnistgov/oscal-content/blob/main/nist.gov/SP800-53/rev5/json/NIST_SP-800-53_rev5_HIGH-baseline_profile.json,NIST Special Publication 800-53 Revision 5 HIGH IMPACT BASELINE,Service,sc-7_smt.a sc-7_smt.b sc-7.3 sc-7.4_smt.a sc-7.5 ia-3,IAM,IAM,,,,,,,,,http://oscal-compass.github.io/compliance-trestle/schemas/oscal/cd
+3000021,iam_admin_role_users_per_account_maxcount,Ensure there are no more than # IAM administrators configured per account,iam_admin_role_users_per_account_maxcount,Check whether there are no more than # IAM administrators configured per account,,,https://github.com/usnistgov/oscal-content/blob/main/nist.gov/SP800-53/rev5/json/NIST_SP-800-53_rev5_HIGH-baseline_profile.json,NIST Special Publication 800-53 Revision 5 HIGH IMPACT BASELINE,Service,ac-6 ac-5_smt.c,IAM,IAM,allowed_admins_per_account,Maximum allowed administrators per,10,10,allowed_admins_per_account2,Maximum allowed administrators per2,20,20,http://oscal-compass.github.io/compliance-trestle/schemas/oscal/cd
+3000022,iam_cos_public_access_disabled,Ensure Cloud Object Storage public access is disabled in IAM settings (not applicable to ACLs managed using S3 APIs),iam_cos_public_access_disabled,Check whether Cloud Object Storage public access is disabled in IAM settings (not applicable to ACLs managed using S3 APIs),,,https://github.com/usnistgov/oscal-content/blob/main/nist.gov/SP800-53/rev5/json/NIST_SP-800-53_rev5_HIGH-baseline_profile.json,NIST Special Publication 800-53 Revision 5 HIGH IMPACT BASELINE,Service,ac-3 ac-4 ac-6 sc-7_smt.a sc-7_smt.b sc-7.4_smt.a ac-14_smt.a cm-7_smt.a cm-7_smt.b,IAM,IAM,,,,,,,,,http://oscal-compass.github.io/compliance-trestle/schemas/oscal/cd
+3000023,iam_account_owner_no_api_key,Ensure the account owner does not have an IBM Cloud API key created in IAM,iam_account_owner_no_api_key,Check whether the account owner does not have an IBM Cloud API key created in IAM,,,https://github.com/usnistgov/oscal-content/blob/main/nist.gov/SP800-53/rev5/json/NIST_SP-800-53_rev5_HIGH-baseline_profile.json,NIST Special Publication 800-53 Revision 5 HIGH IMPACT BASELINE,Service,ac-2_smt.d ac-3 ac-5_smt.c ac-6,IAM,IAM,,,,,,,,,http://oscal-compass.github.io/compliance-trestle/schemas/oscal/cd
+3000024,iam_api_keys_rotation_configured,Ensure IBM Cloud API keys that are managed in IAM are rotated at least every # days,iam_api_keys_rotation_configured,Check whether IBM Cloud API keys that are managed in IAM are rotated at least every # days,,,https://github.com/usnistgov/oscal-content/blob/main/nist.gov/SP800-53/rev5/json/NIST_SP-800-53_rev5_HIGH-baseline_profile.json,NIST Special Publication 800-53 Revision 5 HIGH IMPACT BASELINE,Service,ia-5_smt.g,IAM,IAM,api_keys_rotated_days,API Keys Rotated,"x, y, z",,api_keys_rotated_days2,API Keys Rotated2,"r, s, t",,http://oscal-compass.github.io/compliance-trestle/schemas/oscal/cd
+3000027,iam_account_owner_api_key_restrictions_configured,Ensure permissions for API key creation are limited and configured in IAM settings for the account owner,iam_account_owner_api_key_restrictions_configured,Check whether permissions for API key creation are limited and configured in IAM settings for the account owner,,,https://github.com/usnistgov/oscal-content/blob/main/nist.gov/SP800-53/rev5/json/NIST_SP-800-53_rev5_HIGH-baseline_profile.json,NIST Special Publication 800-53 Revision 5 HIGH IMPACT BASELINE,Service,ac-2_smt.d ac-3 ac-5_smt.c ac-6,IAM,IAM,,,,,,,,,http://oscal-compass.github.io/compliance-trestle/schemas/oscal/cd
+3000029,iam_admin_role__user_maxcount,Ensure IAM-enabled services have no more than # users with the IAM administrator role,iam_admin_role__user_maxcount,Check whether IAM-enabled services have no more than # users with the IAM administrator role,,,https://github.com/usnistgov/oscal-content/blob/main/nist.gov/SP800-53/rev5/json/NIST_SP-800-53_rev5_HIGH-baseline_profile.json,NIST Special Publication 800-53 Revision 5 HIGH IMPACT BASELINE,Service,ac-6 ac-5_smt.c ia-7,IAM,IAM,no_of_admins_for_iam,Maximum no of IAM user,"a, b, c",,no_of_admins_for_iam2,Maximum no of IAM user2,"d, e, f",,http://oscal-compass.github.io/compliance-trestle/schemas/oscal/cd
+3000030,iam_serviceID_policies_attached_to_access_groups_or_roles,Ensure IAM policies for service IDs are attached only to groups or roles,iam_serviceID_policies_attached_to_access_groups_or_roles,Check whether IAM policies for service IDs are attached only to groups or roles,,,https://github.com/usnistgov/oscal-content/blob/main/nist.gov/SP800-53/rev5/json/NIST_SP-800-53_rev5_HIGH-baseline_profile.json,NIST Special Publication 800-53 Revision 5 HIGH IMPACT BASELINE,Service,ac-3 ac-6 ac-2_smt.d ac-5_smt.c ia-7,IAM,IAM,,,,,,,,,http://oscal-compass.github.io/compliance-trestle/schemas/oscal/cd
+3000031,iam_logDNA_enabled,Ensure Identity and Access Management (IAM) is enabled with audit logging,iam_logDNA_enabled,Check whether Identity and Access Management (IAM) is enabled with audit logging,,,https://github.com/usnistgov/oscal-content/blob/main/nist.gov/SP800-53/rev5/json/NIST_SP-800-53_rev5_HIGH-baseline_profile.json,NIST Special Publication 800-53 Revision 5 HIGH IMPACT BASELINE,Service,au-2_smt.a au-2_smt.d si-4_smt.a si-4_smt.b si-4_smt.c au-12_smt.a au-12_smt.b au-12_smt.c au-3 au-8_smt.a au-8_smt.b au-8.1_smt.a au-8.1_smt.b ca-7_smt.d,IAM,IAM,,,,,,,,,http://oscal-compass.github.io/compliance-trestle/schemas/oscal/cd
+3000032,iam_admin_role_serviceid_maxcount,Ensure IAM-enabled services have no more than # service IDs with the IAM administrator role,iam_admin_role_serviceid_maxcount,Check whether IAM-enabled services have no more than # service IDs with the IAM administrator role,,,https://github.com/usnistgov/oscal-content/blob/main/nist.gov/SP800-53/rev5/json/NIST_SP-800-53_rev5_HIGH-baseline_profile.json,NIST Special Publication 800-53 Revision 5 HIGH IMPACT BASELINE,Service,ac-6 ac-5_smt.c ia-7,IAM,IAM,no_of_service_id_admins_for_iam,Maximum no of IAM Service ID,"3, 4, 5",,no_of_service_id_admins_for_iam2,Maximum no of IAM Service ID2,"10, 11, 12",,http://oscal-compass.github.io/compliance-trestle/schemas/oscal/cd
diff --git a/tests/data/json/comp_def_more_params.json b/tests/data/json/comp_def_more_params.json
new file mode 100644
index 00000000..7918febd
--- /dev/null
+++ b/tests/data/json/comp_def_more_params.json
@@ -0,0 +1,503 @@
+{
+  "component-definition": {
+    "uuid": "2652b814-2a6b-4b6d-a0ae-8bc7a007209f",
+    "metadata": {
+      "title": "comp def a",
+      "last-modified": "2021-07-19T14:03:03+00:00",
+      "version": "0.21.0",
+      "oscal-version": "1.0.2",
+      "roles": [
+        {
+          "id": "prepared-by",
+          "title": "Indicates the organization that created this content."
+        },
+        {
+          "id": "prepared-for",
+          "title": "Indicates the organization for which this content was created.."
+        },
+        {
+          "id": "content-approver",
+          "title": "Indicates the organization responsible for all content represented in the \"document\"."
+        }
+      ],
+      "parties": [
+        {
+          "uuid": "ce1f379a-fcdd-485a-a7b7-6f02c0763dd2",
+          "type": "organization",
+          "name": "ACME",
+          "remarks": "ACME company"
+        },
+        {
+          "uuid": "481856b6-16e4-4993-a3ed-2fb242ce235b",
+          "type": "organization",
+          "name": "Customer",
+          "remarks": "Customer for the Component Definition"
+        },
+        {
+          "uuid": "2dc8b17f-daca-44a1-8a1d-c290120ea5e2",
+          "type": "organization",
+          "name": "ISV",
+          "remarks": "ISV for the Component Definition"
+        }
+      ],
+      "responsible-parties": [
+        {
+          "role-id": "prepared-by",
+          "party-uuids": [
+            "ce1f379a-fcdd-485a-a7b7-6f02c0763dd2"
+          ]
+        },
+        {
+          "role-id": "prepared-for",
+          "party-uuids": [
+            "481856b6-16e4-4993-a3ed-2fb242ce235b",
+            "2dc8b17f-daca-44a1-8a1d-c290120ea5e2"
+          ]
+        },
+        {
+          "role-id": "content-approver",
+          "party-uuids": [
+            "ce1f379a-fcdd-485a-a7b7-6f02c0763dd2"
+          ]
+        }
+      ]
+    },
+    "components": [
+      {
+        "uuid": "8220b305-0271-45f9-8a21-40ab6f197f70",
+        "type": "Service",
+        "title": "comp_ca",
+        "description": "comp ca",
+        "props": [
+          {
+            "name": "Rule_Id",
+            "ns": "http://comp_ns",
+            "value": "top_shared_rule_1",
+            "class": "Rule_Id",
+            "remarks": "rule_1"
+          },
+          {
+            "name": "Rule_Description",
+            "ns": "http://comp_ns",
+            "value": "top shared rule 1 in aa",
+            "remarks": "rule_1"
+          },
+          {
+            "name": "Parameter_Id",
+            "ns": "http://comp_ns",
+            "value": "shared_param_x",
+            "class": "Parameter_Id",
+            "remarks": "rule_x"
+          },
+          {
+            "name": "Parameter_Description",
+            "ns": "http://comp_ns",
+            "value": "shared param x in aa",
+            "class": "Parameter_Description",
+            "remarks": "rule_x"
+          },
+          {
+            "name": "Parameter_Value_Alternatives",
+            "ns": "http://comp_ns",
+            "value": "[\"shared_param_x_aa_opt_1\", \"shared_param_x_aa_opt_2\", \"shared_param_x_aa_opt_3\"]",
+            "class": "Parameter_Value_Alternatives",
+            "remarks": "rule_x"
+          }
+        ],
+        "control-implementations": [
+          {
+            "uuid": "76e89b67-3d6b-463d-90df-ec56a46c6069",
+            "source": "trestle://profiles/comp_prof_aa/profile.json",
+            "description": "trestle comp prof aa",
+            "props": [
+              {
+                "name": "profile_name",
+                "ns": "https://trestle/prof_ns",
+                "value": "trestle prof aa",
+                "class": "trestle_profile_name"
+              },
+              {
+                "name": "Rule_Id",
+                "ns": "http://comp_ns",
+                "value": "comp_rule_aa_1",
+                "class": "Rule_Id",
+                "remarks": "rule_2"
+              },
+              {
+                "name": "Rule_Description",
+                "ns": "http://comp_ns",
+                "value": "comp rule aa 1",
+                "remarks": "rule_2"
+              },
+              {
+                "name": "Rule_Id",
+                "ns": "http://comp_ns",
+                "value": "comp_rule_aa_2",
+                "class": "Rule_Id",
+                "remarks": "rule_3"
+              },
+              {
+                "name": "Rule_Description",
+                "ns": "http://comp_ns",
+                "value": "comp rule aa 2",
+                "class": "Rule_Description",
+                "remarks": "rule_3"
+              },
+              {
+                "name": "Parameter_Id",
+                "ns": "http://comp_ns",
+                "value": "shared_param_for_ca_1",
+                "class": "Parameter_Id",
+                "remarks": "rule_1"
+              },
+              {
+                "name": "Parameter_Description",
+                "ns": "http://comp_ns",
+                "value": "shared param 1 in aa",
+                "class": "Parameter_Description",
+                "remarks": "rule_1"
+              },
+              {
+                "name": "Parameter_Value_Alternatives",
+                "ns": "http://comp_ns",
+                "value": "[\"shared_param_for_ca_1_ca_opt_1\", \"shared_param_for_ca_1_ca_opt_2\", \"shared_param_for_ca_1_ca_opt_3\"]",
+                "class": "Parameter_Value_Alternatives",
+                "remarks": "rule_1"
+              },
+              {
+                "name": "Parameter_Id2",
+                "ns": "http://oscal-compass.github.io/compliance-trestle/schemas/oscal/cd",
+                "value": "allowed_admins_per_account2",
+                "remarks": "rule_1"
+              },
+              {
+                "name": "Parameter_Description2",
+                "ns": "http://oscal-compass.github.io/compliance-trestle/schemas/oscal/cd",
+                "value": "Maximum allowed administrators per2",
+                "remarks": "rule_1"
+              },
+              {
+                "name": "Parameter_Value_Default2",
+                "ns": "http://oscal-compass.github.io/compliance-trestle/schemas/oscal/cd",
+                "value": "20",
+                "remarks": "rule_1"
+              },
+              {
+                "name": "Parameter_Value_Alternatives2",
+                "ns": "http://oscal-compass.github.io/compliance-trestle/schemas/oscal/cd",
+                "value": "[\"20\", \"30\"]",
+                "remarks": "rule_1"
+              }
+            ],
+            "set-parameters": [
+              {
+                "param-id": "shared_param_for_ca_1",
+                "values": [
+                  "shared_param_for_ca_1_ca_opt_1"
+                ],
+                "remarks": "set shared param aa 3"
+              },
+              {
+                "param-id": "ac-1_prm_3",
+                "values": [
+                  "set by comp aa ci"
+                ]
+              }
+            ],
+            "implemented-requirements": [
+              {
+                "uuid": "ca5ea4c5-ba51-4b1d-932a-5606891b7500",
+                "control-id": "ac-1",
+                "description": "imp req prose for ac-1 from comp aa",
+                "props": [
+                  {
+                    "name": "Rule_Id",
+                    "value": "top_shared_rule_1"
+                  },
+                  {
+                    "name": "implementation-status",
+                    "value": "implemented"
+                  }
+                ],
+                "set-parameters": [
+                  {
+                    "param-id": "shared_param_for_ca_1",
+                    "values": [
+                      "shared_param_for_ca_1_ca_opt_1"
+                    ],
+                    "remarks": "set shared param aa 1"
+                  },
+                  {
+                    "param-id": "ac-1_prm_3",
+                    "values": [
+                      "set by comp aa imp req"
+                    ]
+                  }
+                ],    
+                "responsible-roles": [
+                  {
+                    "role-id": "prepared-by",
+                    "party-uuids": [
+                      "ce1f379a-fcdd-485a-a7b7-6f02c0763dd2"
+                    ]
+                  },
+                  {
+                    "role-id": "prepared-for",
+                    "party-uuids": [
+                      "481856b6-16e4-4993-a3ed-2fb242ce235b",
+                      "2dc8b17f-daca-44a1-8a1d-c290120ea5e2"
+                    ]
+                  },
+                  {
+                    "role-id": "content-approver",
+                    "party-uuids": [
+                      "ce1f379a-fcdd-485a-a7b7-6f02c0763dd2"
+                    ]
+                  }
+                ],
+                "statements": [
+                  {
+                    "statement-id": "ac-1_smt.a",
+                    "uuid": "2652b814-2a6b-4b6d-a0ae-8bc7a0072200",
+                    "description": "statement prose for part a. from comp aa",
+                    "props": [
+                      {
+                        "name": "Rule_Id",
+                        "value": "comp_rule_aa_1"
+                      },
+                      {
+                        "name": "implementation-status",
+                        "value": "partial"
+                      }
+                    ]
+                  }
+                ]
+              },
+              {
+                "uuid": "ca5ea4c5-ba51-4b1d-932a-5606891b7599",
+                "control-id": "ac-3",
+                "description": "imp req prose for ac-3 from comp aa",
+                "props": [
+                  {
+                    "name": "Rule_Id",
+                    "value": "top_shared_rule_1"
+                  },
+                  {
+                    "name": "implementation-status",
+                    "value": "implemented"
+                  }
+                ]
+              }
+            ]
+         }
+        ]
+      },
+      {
+        "uuid": "8220b305-0271-45f9-8a21-40ab6f197f71",
+        "type": "Service",
+        "title": "comp_cc",
+        "description": "comp cc",
+        "props": [
+          {
+            "name": "Rule_Id",
+            "ns": "http://comp_ns",
+            "value": "top_shared_rule_1",
+            "class": "Rule_Id",
+            "remarks": "rule_1"
+          },
+          {
+            "name": "Rule_Description",
+            "ns": "http://comp_ns",
+            "value": "top shared rule 1 in ab",
+            "remarks": "rule_1"
+          }
+        ],
+        "control-implementations": [
+          {
+            "uuid": "76e89b67-3d6b-463d-90df-ec56a46c6069",
+            "source": "trestle://profiles/comp_prof_ab/profile.json",
+            "description": "trestle comp prof ab",
+            "props": [
+              {
+                "name": "profile_name",
+                "ns": "https://trestle/prof_ns",
+                "value": "trestle prof ab",
+                "class": "trestle_profile_name"
+              },
+              {
+                "name": "Rule_Id",
+                "ns": "http://comp_ns",
+                "value": "comp_rule_ab_1",
+                "class": "Rule_Id",
+                "remarks": "rule_2"
+              },
+              {
+                "name": "Rule_Description",
+                "ns": "http://comp_ns",
+                "value": "comp rule ab 1",
+                "remarks": "rule_2"
+              },
+              {
+                "name": "Rule_Id",
+                "ns": "http://comp_ns",
+                "value": "comp_rule_ab_5",
+                "class": "Rule_Id",
+                "remarks": "rule_5"
+              },
+              {
+                "name": "Rule_Description",
+                "ns": "http://comp_ns",
+                "value": "comp rule ab 5",
+                "class": "Rule_Description",
+                "remarks": "rule_5"
+              },
+              {
+                "name": "Parameter_Id",
+                "ns": "http://comp_ns",
+                "value": "shared_param_for_ca_1",
+                "class": "Parameter_Id",
+                "remarks": "rule_1"
+              },
+              {
+                "name": "Parameter_Description",
+                "ns": "http://comp_ns",
+                "value": "shared param 1 in ab",
+                "class": "Parameter_Description",
+                "remarks": "rule_1"
+              },
+              {
+                "name": "Parameter_Value_Alternatives",
+                "ns": "http://comp_ns",
+                "value": "[\"shared_param_for_ca_1_cc_opt_1\", \"shared_param_for_ca_1_cc_opt_2\"]",
+                "class": "Parameter_Value_Alternatives",
+                "remarks": "rule_1"
+              }
+            ],
+            "set-parameters": [
+              {
+                "param-id": "shared_param_for_ca_1",
+                "values": [
+                  "shared_param_for_ca_1_cc_opt_1"
+                ],
+                "remarks": "set param ab 1"
+              }
+            ],
+            "implemented-requirements": [
+              {
+                "uuid": "ca5ea4c5-ba51-4b1d-932a-5606891b7501",
+                "control-id": "ac-1",
+                "description": "",
+                "props": [
+                  {
+                    "name": "Rule_Id",
+                    "value": "top_shared_rule_1"
+                  },
+                  {
+                    "name": "implementation-status",
+                    "value": "implemented"
+                  }
+                ],
+                "responsible-roles": [
+                  {
+                    "role-id": "prepared-by",
+                    "party-uuids": [
+                      "ce1f379a-fcdd-485a-a7b7-6f02c0763dd2"
+                    ]
+                  },
+                  {
+                    "role-id": "prepared-for",
+                    "party-uuids": [
+                      "481856b6-16e4-4993-a3ed-2fb242ce235b",
+                      "2dc8b17f-daca-44a1-8a1d-c290120ea5e2"
+                    ]
+                  },
+                  {
+                    "role-id": "content-approver",
+                    "party-uuids": [
+                      "ce1f379a-fcdd-485a-a7b7-6f02c0763dd2"
+                    ]
+                  }
+                ],
+                "statements": [
+                  {
+                    "statement-id": "ac-1_smt.a",
+                    "uuid": "2652b814-2a6b-4b6d-a0ae-8bc7a0072201",
+                    "description": "",
+                    "props": [
+                      {
+                        "name": "Rule_Id",
+                        "value": "comp_rule_ab_1"
+                      },
+                      {
+                        "name": "implementation-status",
+                        "value": "partial"
+                      }
+                    ]
+                  },
+                  {
+                    "statement-id": "ac-1_smt.b",
+                    "uuid": "2652b814-2a6b-4b6d-a0ae-8bc7a0072209",
+                    "description": ""
+                  }
+                ]
+              },
+              {
+                "uuid": "ca5ea4c5-ba51-4b1d-932a-5606891b7502",
+                "control-id": "at-1",
+                "description": "imp req prose for at-1 from comp ab",
+                "props": [
+                  {
+                    "name": "Rule_Id",
+                    "value": "rule_1"
+                  },
+                  {
+                    "name": "implementation-status",
+                    "value": "implemented"
+                  }
+                ],
+                "responsible-roles": [
+                  {
+                    "role-id": "prepared-by",
+                    "party-uuids": [
+                      "ce1f379a-fcdd-485a-a7b7-6f02c0763dd2"
+                    ]
+                  },
+                  {
+                    "role-id": "prepared-for",
+                    "party-uuids": [
+                      "481856b6-16e4-4993-a3ed-2fb242ce235b",
+                      "2dc8b17f-daca-44a1-8a1d-c290120ea5e2"
+                    ]
+                  },
+                  {
+                    "role-id": "content-approver",
+                    "party-uuids": [
+                      "ce1f379a-fcdd-485a-a7b7-6f02c0763dd2"
+                    ]
+                  }
+                ],
+                "statements": [
+                  {
+                    "statement-id": "at-1_smt.b",
+                    "uuid": "2652b814-2a6b-4b6d-a0ae-8bc7a0072202",
+                    "description": "statement prose for part b. from comp ab",
+                    "props": [
+                      {
+                        "name": "Rule_Id",
+                        "value": "rule_5"
+                      },
+                      {
+                        "name": "implementation-status",
+                        "value": "implemented"
+                      }
+                    ]
+                  }
+                ]
+              }
+            ]
+         }
+        ]
+      }
+    ]
+  }
+}
\ No newline at end of file
diff --git a/tests/data/json/comp_def_more_params_dup.json b/tests/data/json/comp_def_more_params_dup.json
new file mode 100644
index 00000000..7918febd
--- /dev/null
+++ b/tests/data/json/comp_def_more_params_dup.json
@@ -0,0 +1,503 @@
+{
+  "component-definition": {
+    "uuid": "2652b814-2a6b-4b6d-a0ae-8bc7a007209f",
+    "metadata": {
+      "title": "comp def a",
+      "last-modified": "2021-07-19T14:03:03+00:00",
+      "version": "0.21.0",
+      "oscal-version": "1.0.2",
+      "roles": [
+        {
+          "id": "prepared-by",
+          "title": "Indicates the organization that created this content."
+        },
+        {
+          "id": "prepared-for",
+          "title": "Indicates the organization for which this content was created.."
+        },
+        {
+          "id": "content-approver",
+          "title": "Indicates the organization responsible for all content represented in the \"document\"."
+        }
+      ],
+      "parties": [
+        {
+          "uuid": "ce1f379a-fcdd-485a-a7b7-6f02c0763dd2",
+          "type": "organization",
+          "name": "ACME",
+          "remarks": "ACME company"
+        },
+        {
+          "uuid": "481856b6-16e4-4993-a3ed-2fb242ce235b",
+          "type": "organization",
+          "name": "Customer",
+          "remarks": "Customer for the Component Definition"
+        },
+        {
+          "uuid": "2dc8b17f-daca-44a1-8a1d-c290120ea5e2",
+          "type": "organization",
+          "name": "ISV",
+          "remarks": "ISV for the Component Definition"
+        }
+      ],
+      "responsible-parties": [
+        {
+          "role-id": "prepared-by",
+          "party-uuids": [
+            "ce1f379a-fcdd-485a-a7b7-6f02c0763dd2"
+          ]
+        },
+        {
+          "role-id": "prepared-for",
+          "party-uuids": [
+            "481856b6-16e4-4993-a3ed-2fb242ce235b",
+            "2dc8b17f-daca-44a1-8a1d-c290120ea5e2"
+          ]
+        },
+        {
+          "role-id": "content-approver",
+          "party-uuids": [
+            "ce1f379a-fcdd-485a-a7b7-6f02c0763dd2"
+          ]
+        }
+      ]
+    },
+    "components": [
+      {
+        "uuid": "8220b305-0271-45f9-8a21-40ab6f197f70",
+        "type": "Service",
+        "title": "comp_ca",
+        "description": "comp ca",
+        "props": [
+          {
+            "name": "Rule_Id",
+            "ns": "http://comp_ns",
+            "value": "top_shared_rule_1",
+            "class": "Rule_Id",
+            "remarks": "rule_1"
+          },
+          {
+            "name": "Rule_Description",
+            "ns": "http://comp_ns",
+            "value": "top shared rule 1 in aa",
+            "remarks": "rule_1"
+          },
+          {
+            "name": "Parameter_Id",
+            "ns": "http://comp_ns",
+            "value": "shared_param_x",
+            "class": "Parameter_Id",
+            "remarks": "rule_x"
+          },
+          {
+            "name": "Parameter_Description",
+            "ns": "http://comp_ns",
+            "value": "shared param x in aa",
+            "class": "Parameter_Description",
+            "remarks": "rule_x"
+          },
+          {
+            "name": "Parameter_Value_Alternatives",
+            "ns": "http://comp_ns",
+            "value": "[\"shared_param_x_aa_opt_1\", \"shared_param_x_aa_opt_2\", \"shared_param_x_aa_opt_3\"]",
+            "class": "Parameter_Value_Alternatives",
+            "remarks": "rule_x"
+          }
+        ],
+        "control-implementations": [
+          {
+            "uuid": "76e89b67-3d6b-463d-90df-ec56a46c6069",
+            "source": "trestle://profiles/comp_prof_aa/profile.json",
+            "description": "trestle comp prof aa",
+            "props": [
+              {
+                "name": "profile_name",
+                "ns": "https://trestle/prof_ns",
+                "value": "trestle prof aa",
+                "class": "trestle_profile_name"
+              },
+              {
+                "name": "Rule_Id",
+                "ns": "http://comp_ns",
+                "value": "comp_rule_aa_1",
+                "class": "Rule_Id",
+                "remarks": "rule_2"
+              },
+              {
+                "name": "Rule_Description",
+                "ns": "http://comp_ns",
+                "value": "comp rule aa 1",
+                "remarks": "rule_2"
+              },
+              {
+                "name": "Rule_Id",
+                "ns": "http://comp_ns",
+                "value": "comp_rule_aa_2",
+                "class": "Rule_Id",
+                "remarks": "rule_3"
+              },
+              {
+                "name": "Rule_Description",
+                "ns": "http://comp_ns",
+                "value": "comp rule aa 2",
+                "class": "Rule_Description",
+                "remarks": "rule_3"
+              },
+              {
+                "name": "Parameter_Id",
+                "ns": "http://comp_ns",
+                "value": "shared_param_for_ca_1",
+                "class": "Parameter_Id",
+                "remarks": "rule_1"
+              },
+              {
+                "name": "Parameter_Description",
+                "ns": "http://comp_ns",
+                "value": "shared param 1 in aa",
+                "class": "Parameter_Description",
+                "remarks": "rule_1"
+              },
+              {
+                "name": "Parameter_Value_Alternatives",
+                "ns": "http://comp_ns",
+                "value": "[\"shared_param_for_ca_1_ca_opt_1\", \"shared_param_for_ca_1_ca_opt_2\", \"shared_param_for_ca_1_ca_opt_3\"]",
+                "class": "Parameter_Value_Alternatives",
+                "remarks": "rule_1"
+              },
+              {
+                "name": "Parameter_Id2",
+                "ns": "http://oscal-compass.github.io/compliance-trestle/schemas/oscal/cd",
+                "value": "allowed_admins_per_account2",
+                "remarks": "rule_1"
+              },
+              {
+                "name": "Parameter_Description2",
+                "ns": "http://oscal-compass.github.io/compliance-trestle/schemas/oscal/cd",
+                "value": "Maximum allowed administrators per2",
+                "remarks": "rule_1"
+              },
+              {
+                "name": "Parameter_Value_Default2",
+                "ns": "http://oscal-compass.github.io/compliance-trestle/schemas/oscal/cd",
+                "value": "20",
+                "remarks": "rule_1"
+              },
+              {
+                "name": "Parameter_Value_Alternatives2",
+                "ns": "http://oscal-compass.github.io/compliance-trestle/schemas/oscal/cd",
+                "value": "[\"20\", \"30\"]",
+                "remarks": "rule_1"
+              }
+            ],
+            "set-parameters": [
+              {
+                "param-id": "shared_param_for_ca_1",
+                "values": [
+                  "shared_param_for_ca_1_ca_opt_1"
+                ],
+                "remarks": "set shared param aa 3"
+              },
+              {
+                "param-id": "ac-1_prm_3",
+                "values": [
+                  "set by comp aa ci"
+                ]
+              }
+            ],
+            "implemented-requirements": [
+              {
+                "uuid": "ca5ea4c5-ba51-4b1d-932a-5606891b7500",
+                "control-id": "ac-1",
+                "description": "imp req prose for ac-1 from comp aa",
+                "props": [
+                  {
+                    "name": "Rule_Id",
+                    "value": "top_shared_rule_1"
+                  },
+                  {
+                    "name": "implementation-status",
+                    "value": "implemented"
+                  }
+                ],
+                "set-parameters": [
+                  {
+                    "param-id": "shared_param_for_ca_1",
+                    "values": [
+                      "shared_param_for_ca_1_ca_opt_1"
+                    ],
+                    "remarks": "set shared param aa 1"
+                  },
+                  {
+                    "param-id": "ac-1_prm_3",
+                    "values": [
+                      "set by comp aa imp req"
+                    ]
+                  }
+                ],    
+                "responsible-roles": [
+                  {
+                    "role-id": "prepared-by",
+                    "party-uuids": [
+                      "ce1f379a-fcdd-485a-a7b7-6f02c0763dd2"
+                    ]
+                  },
+                  {
+                    "role-id": "prepared-for",
+                    "party-uuids": [
+                      "481856b6-16e4-4993-a3ed-2fb242ce235b",
+                      "2dc8b17f-daca-44a1-8a1d-c290120ea5e2"
+                    ]
+                  },
+                  {
+                    "role-id": "content-approver",
+                    "party-uuids": [
+                      "ce1f379a-fcdd-485a-a7b7-6f02c0763dd2"
+                    ]
+                  }
+                ],
+                "statements": [
+                  {
+                    "statement-id": "ac-1_smt.a",
+                    "uuid": "2652b814-2a6b-4b6d-a0ae-8bc7a0072200",
+                    "description": "statement prose for part a. from comp aa",
+                    "props": [
+                      {
+                        "name": "Rule_Id",
+                        "value": "comp_rule_aa_1"
+                      },
+                      {
+                        "name": "implementation-status",
+                        "value": "partial"
+                      }
+                    ]
+                  }
+                ]
+              },
+              {
+                "uuid": "ca5ea4c5-ba51-4b1d-932a-5606891b7599",
+                "control-id": "ac-3",
+                "description": "imp req prose for ac-3 from comp aa",
+                "props": [
+                  {
+                    "name": "Rule_Id",
+                    "value": "top_shared_rule_1"
+                  },
+                  {
+                    "name": "implementation-status",
+                    "value": "implemented"
+                  }
+                ]
+              }
+            ]
+         }
+        ]
+      },
+      {
+        "uuid": "8220b305-0271-45f9-8a21-40ab6f197f71",
+        "type": "Service",
+        "title": "comp_cc",
+        "description": "comp cc",
+        "props": [
+          {
+            "name": "Rule_Id",
+            "ns": "http://comp_ns",
+            "value": "top_shared_rule_1",
+            "class": "Rule_Id",
+            "remarks": "rule_1"
+          },
+          {
+            "name": "Rule_Description",
+            "ns": "http://comp_ns",
+            "value": "top shared rule 1 in ab",
+            "remarks": "rule_1"
+          }
+        ],
+        "control-implementations": [
+          {
+            "uuid": "76e89b67-3d6b-463d-90df-ec56a46c6069",
+            "source": "trestle://profiles/comp_prof_ab/profile.json",
+            "description": "trestle comp prof ab",
+            "props": [
+              {
+                "name": "profile_name",
+                "ns": "https://trestle/prof_ns",
+                "value": "trestle prof ab",
+                "class": "trestle_profile_name"
+              },
+              {
+                "name": "Rule_Id",
+                "ns": "http://comp_ns",
+                "value": "comp_rule_ab_1",
+                "class": "Rule_Id",
+                "remarks": "rule_2"
+              },
+              {
+                "name": "Rule_Description",
+                "ns": "http://comp_ns",
+                "value": "comp rule ab 1",
+                "remarks": "rule_2"
+              },
+              {
+                "name": "Rule_Id",
+                "ns": "http://comp_ns",
+                "value": "comp_rule_ab_5",
+                "class": "Rule_Id",
+                "remarks": "rule_5"
+              },
+              {
+                "name": "Rule_Description",
+                "ns": "http://comp_ns",
+                "value": "comp rule ab 5",
+                "class": "Rule_Description",
+                "remarks": "rule_5"
+              },
+              {
+                "name": "Parameter_Id",
+                "ns": "http://comp_ns",
+                "value": "shared_param_for_ca_1",
+                "class": "Parameter_Id",
+                "remarks": "rule_1"
+              },
+              {
+                "name": "Parameter_Description",
+                "ns": "http://comp_ns",
+                "value": "shared param 1 in ab",
+                "class": "Parameter_Description",
+                "remarks": "rule_1"
+              },
+              {
+                "name": "Parameter_Value_Alternatives",
+                "ns": "http://comp_ns",
+                "value": "[\"shared_param_for_ca_1_cc_opt_1\", \"shared_param_for_ca_1_cc_opt_2\"]",
+                "class": "Parameter_Value_Alternatives",
+                "remarks": "rule_1"
+              }
+            ],
+            "set-parameters": [
+              {
+                "param-id": "shared_param_for_ca_1",
+                "values": [
+                  "shared_param_for_ca_1_cc_opt_1"
+                ],
+                "remarks": "set param ab 1"
+              }
+            ],
+            "implemented-requirements": [
+              {
+                "uuid": "ca5ea4c5-ba51-4b1d-932a-5606891b7501",
+                "control-id": "ac-1",
+                "description": "",
+                "props": [
+                  {
+                    "name": "Rule_Id",
+                    "value": "top_shared_rule_1"
+                  },
+                  {
+                    "name": "implementation-status",
+                    "value": "implemented"
+                  }
+                ],
+                "responsible-roles": [
+                  {
+                    "role-id": "prepared-by",
+                    "party-uuids": [
+                      "ce1f379a-fcdd-485a-a7b7-6f02c0763dd2"
+                    ]
+                  },
+                  {
+                    "role-id": "prepared-for",
+                    "party-uuids": [
+                      "481856b6-16e4-4993-a3ed-2fb242ce235b",
+                      "2dc8b17f-daca-44a1-8a1d-c290120ea5e2"
+                    ]
+                  },
+                  {
+                    "role-id": "content-approver",
+                    "party-uuids": [
+                      "ce1f379a-fcdd-485a-a7b7-6f02c0763dd2"
+                    ]
+                  }
+                ],
+                "statements": [
+                  {
+                    "statement-id": "ac-1_smt.a",
+                    "uuid": "2652b814-2a6b-4b6d-a0ae-8bc7a0072201",
+                    "description": "",
+                    "props": [
+                      {
+                        "name": "Rule_Id",
+                        "value": "comp_rule_ab_1"
+                      },
+                      {
+                        "name": "implementation-status",
+                        "value": "partial"
+                      }
+                    ]
+                  },
+                  {
+                    "statement-id": "ac-1_smt.b",
+                    "uuid": "2652b814-2a6b-4b6d-a0ae-8bc7a0072209",
+                    "description": ""
+                  }
+                ]
+              },
+              {
+                "uuid": "ca5ea4c5-ba51-4b1d-932a-5606891b7502",
+                "control-id": "at-1",
+                "description": "imp req prose for at-1 from comp ab",
+                "props": [
+                  {
+                    "name": "Rule_Id",
+                    "value": "rule_1"
+                  },
+                  {
+                    "name": "implementation-status",
+                    "value": "implemented"
+                  }
+                ],
+                "responsible-roles": [
+                  {
+                    "role-id": "prepared-by",
+                    "party-uuids": [
+                      "ce1f379a-fcdd-485a-a7b7-6f02c0763dd2"
+                    ]
+                  },
+                  {
+                    "role-id": "prepared-for",
+                    "party-uuids": [
+                      "481856b6-16e4-4993-a3ed-2fb242ce235b",
+                      "2dc8b17f-daca-44a1-8a1d-c290120ea5e2"
+                    ]
+                  },
+                  {
+                    "role-id": "content-approver",
+                    "party-uuids": [
+                      "ce1f379a-fcdd-485a-a7b7-6f02c0763dd2"
+                    ]
+                  }
+                ],
+                "statements": [
+                  {
+                    "statement-id": "at-1_smt.b",
+                    "uuid": "2652b814-2a6b-4b6d-a0ae-8bc7a0072202",
+                    "description": "statement prose for part b. from comp ab",
+                    "props": [
+                      {
+                        "name": "Rule_Id",
+                        "value": "rule_5"
+                      },
+                      {
+                        "name": "implementation-status",
+                        "value": "implemented"
+                      }
+                    ]
+                  }
+                ]
+              }
+            ]
+         }
+        ]
+      }
+    ]
+  }
+}
\ No newline at end of file
diff --git a/tests/data/json/full_profile_rev5.json b/tests/data/json/full_profile_rev5.json
index 233ece5f..895fffcd 100644
--- a/tests/data/json/full_profile_rev5.json
+++ b/tests/data/json/full_profile_rev5.json
@@ -99,6 +99,8 @@
         "include-controls": [
           {
             "with-ids": [
+              "ac-1",
+              "ac-2.3",
               "sc-21"
             ]
           }
diff --git a/tests/data/json/test_compdef_rev5.json b/tests/data/json/test_compdef_rev5.json
new file mode 100644
index 00000000..e0c5cb61
--- /dev/null
+++ b/tests/data/json/test_compdef_rev5.json
@@ -0,0 +1,127 @@
+{
+  "component-definition": {
+    "uuid": "2652b814-2a6b-4b6d-a0ae-8bc7a007209f",
+    "metadata": {
+      "title": "example FedRAMP component definition",
+      "last-modified": "2021-07-19T14:03:03+00:00",
+      "version": "0.21.0",
+      "oscal-version": "1.0.2",
+      "roles": [
+        {
+          "id": "prepared-by",
+          "title": "Indicates the organization that created this content."
+        },
+        {
+          "id": "prepared-for",
+          "title": "Indicates the organization for which this content was created.."
+        },
+        {
+          "id": "content-approver",
+          "title": "Indicates the organization responsible for all content represented in the \"document\"."
+        }
+      ],
+      "parties": [
+        {
+          "uuid": "ce1f379a-fcdd-485a-a7b7-6f02c0763dd2",
+          "type": "organization",
+          "name": "ACME",
+          "remarks": "ACME company"
+        },
+        {
+          "uuid": "481856b6-16e4-4993-a3ed-2fb242ce235b",
+          "type": "organization",
+          "name": "Customer",
+          "remarks": "Customer for the Component Definition"
+        },
+        {
+          "uuid": "2dc8b17f-daca-44a1-8a1d-c290120ea5e2",
+          "type": "organization",
+          "name": "ISV",
+          "remarks": "ISV for the Component Definition"
+        }
+      ],
+      "responsible-parties": [
+        {
+          "role-id": "prepared-by",
+          "party-uuids": [
+            "ce1f379a-fcdd-485a-a7b7-6f02c0763dd2"
+          ]
+        },
+        {
+          "role-id": "prepared-for",
+          "party-uuids": [
+            "481856b6-16e4-4993-a3ed-2fb242ce235b",
+            "2dc8b17f-daca-44a1-8a1d-c290120ea5e2"
+          ]
+        },
+        {
+          "role-id": "content-approver",
+          "party-uuids": [
+            "ce1f379a-fcdd-485a-a7b7-6f02c0763dd2"
+          ]
+        }
+      ]
+    },
+    "components": [
+      {
+        "uuid": "8220b305-0271-45f9-8a21-40ab6f197f75",
+        "type": "Service",
+        "title": "test_component",
+        "description": "This is a test component",
+        "control-implementations": [
+          {
+            "uuid": "76e89b67-3d6b-463d-90df-ec56a46c6069",
+            "source": "trestle://profiles/full_profile_rev5/profile.json",
+            "description": "trestle comp rev5",
+            "implemented-requirements": [
+              {
+                "uuid": "ca5ea4c5-ba51-4b1d-932a-5606891b7600",
+                "control-id": "sc-21",
+                "description": "imp req prose for sc-21 from test component",
+                "props": [
+                  {
+                    "name": "Rule_Id",
+                    "value": "top_shared_rule_1"
+                  },
+                  {
+                    "name": "implementation-status",
+                    "value": "implemented"
+                  },
+                  {
+                    "name": "control-origination",
+                    "value": "system-specific"
+                  },
+                  {
+                    "name": "control-origination",
+                    "value": "customer-configured"
+                  }
+                ],
+                "responsible-roles": [
+                  {
+                    "role-id": "prepared-by",
+                    "party-uuids": [
+                      "ce1f379a-fcdd-485a-a7b7-6f02c0763dd2"
+                    ]
+                  },
+                  {
+                    "role-id": "prepared-for",
+                    "party-uuids": [
+                      "481856b6-16e4-4993-a3ed-2fb242ce235b",
+                      "2dc8b17f-daca-44a1-8a1d-c290120ea5e2"
+                    ]
+                  },
+                  {
+                    "role-id": "content-approver",
+                    "party-uuids": [
+                      "ce1f379a-fcdd-485a-a7b7-6f02c0763dd2"
+                    ]
+                  }
+                ]
+              }
+            ]
+         }
+        ]
+      }
+    ]
+  }
+}
\ No newline at end of file
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 50244db2..354d17a9 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -369,9 +369,8 @@ def setup_for_component_definition(tmp_trestle_dir: pathlib.Path, monkeypatch: M
     return comp_name
 
 
-def setup_component_generate(tmp_trestle_dir: pathlib.Path) -> str:
+def setup_component_generate(tmp_trestle_dir: pathlib.Path, comp_name='comp_def_a') -> str:
     """Create the compdef, profile and catalog content component-generate."""
-    comp_name = 'comp_def_a'
     load_from_json(tmp_trestle_dir, comp_name, comp_name, comp.ComponentDefinition)
     for prof_name in 'comp_prof,comp_prof_aa,comp_prof_ab,comp_prof_ba,comp_prof_bb'.split(','):
         load_from_json(tmp_trestle_dir, prof_name, prof_name, prof.Profile)
@@ -461,10 +460,10 @@ def setup_for_ssp(
     prof_name: str,
     output_name: str,
     use_yaml: bool = False,
-    leveraged_ssp_name: str = ''
+    leveraged_ssp_name: str = '',
+    comp_names='comp_def_a,comp_def_b'
 ) -> Tuple[argparse.Namespace, pathlib.Path]:
     """Create the comp_def, profile and catalog content needed for ssp-generate."""
-    comp_names = 'comp_def_a,comp_def_b'
     for comp_name in comp_names.split(','):
         load_from_json(tmp_trestle_dir, comp_name, comp_name, comp.ComponentDefinition)
     prof_name_list = [prof_name]
@@ -493,6 +492,32 @@ def setup_for_ssp(
     return args, yaml_path
 
 
+def setup_for_ssp_fedramp(
+    tmp_trestle_dir: pathlib.Path,
+    output_name: str,
+) -> argparse.Namespace:
+    """Load profile and component needed for ssp-generate with FedRAMP profile."""
+    prof_name = 'full_profile_rev5'
+    comp_name = 'test_compdef_rev5'
+    load_from_json(tmp_trestle_dir, prof_name, prof_name, prof.Profile)
+    load_from_json(tmp_trestle_dir, comp_name, comp_name, comp.ComponentDefinition)
+
+    args = argparse.Namespace(
+        trestle_root=tmp_trestle_dir,
+        profile=prof_name,
+        compdefs=comp_name,
+        leveraged_ssp=None,
+        output=output_name,
+        verbose=0,
+        overwrite_header_values=False,
+        yaml_header=None,
+        allowed_sections=None,
+        force_overwrite=None
+    )
+
+    return args
+
+
 def make_file_hidden(file_path: pathlib.Path, if_dot=False) -> None:
     """
     Make a file hidden on windows.
diff --git a/tests/trestle/core/commands/author/component_test.py b/tests/trestle/core/commands/author/component_test.py
index 2421ace3..508b14f7 100644
--- a/tests/trestle/core/commands/author/component_test.py
+++ b/tests/trestle/core/commands/author/component_test.py
@@ -187,3 +187,18 @@ def test_component_generate_missing_control(tmp_trestle_dir: pathlib.Path, monke
     test_utils.execute_command_and_assert(generate_cmd, 0, monkeypatch)
     _, err = capsys.readouterr()
     assert "Component comp_aa references controls {'ac-1'} not in profile." in err
+
+
+def test_component_generate_more_than_one_param(tmp_trestle_dir: pathlib.Path, monkeypatch: MonkeyPatch) -> None:
+    """Test component generate with more than 1 parameters per rule."""
+    comp_name = test_utils.setup_component_generate(tmp_trestle_dir, 'comp_def_more_params')
+
+    generate_cmd = f'trestle author component-generate -n {comp_name} -o {md_path}'
+
+    # generate the md first time
+    test_utils.execute_command_and_assert(generate_cmd, CmdReturnCodes.SUCCESS.value, monkeypatch)
+
+    assem_name = 'assem_comp'
+    # now assemble component generated
+    assemble_cmd = f'trestle author component-assemble -m {md_path} -n {comp_name} -o {assem_name}'
+    test_utils.execute_command_and_assert(assemble_cmd, CmdReturnCodes.SUCCESS.value, monkeypatch)
diff --git a/tests/trestle/core/commands/author/ssp_test.py b/tests/trestle/core/commands/author/ssp_test.py
index 6f2646a1..8f9eaf55 100644
--- a/tests/trestle/core/commands/author/ssp_test.py
+++ b/tests/trestle/core/commands/author/ssp_test.py
@@ -19,7 +19,7 @@ import pathlib
 from _pytest.monkeypatch import MonkeyPatch
 
 from tests import test_utils
-from tests.test_utils import FileChecker, setup_for_ssp
+from tests.test_utils import FileChecker, setup_for_ssp, setup_for_ssp_fedramp
 
 import trestle.core.generators as gens
 import trestle.core.generic_oscal as generic
@@ -485,6 +485,17 @@ def test_ssp_assemble(tmp_trestle_dir: pathlib.Path) -> None:
     assert orig_ssp_path.stat().st_mtime > orig_file_creation
 
 
+def test_ssp_assemble_fedramp_profile(tmp_trestle_dir: pathlib.Path, monkeypatch: MonkeyPatch) -> None:
+    """Tests ssp assemble with a fedramp profile."""
+    gen_args = setup_for_ssp_fedramp(tmp_trestle_dir, ssp_name)
+    ssp_gen = SSPGenerate()
+    assert ssp_gen._run(gen_args) == 0
+
+    # first assemble
+    ssp_assemble = f'trestle author ssp-assemble -m {ssp_name} -o {ssp_name} -cd {gen_args.compdefs}'
+    test_utils.execute_command_and_assert(ssp_assemble, 0, monkeypatch)
+
+
 def test_ssp_assemble_remove_comp_defs(tmp_trestle_dir: pathlib.Path, monkeypatch: MonkeyPatch) -> None:
     """Tests the removal of component definitions that are no longer valid for an ssp."""
     gen_args, _ = setup_for_ssp(tmp_trestle_dir, prof_name, ssp_name)
@@ -1044,3 +1055,61 @@ def test_ssp_assemble_no_comps(tmp_trestle_dir: pathlib.Path, capsys) -> None:
     _, err = capsys.readouterr()
     assert 'Control ac-1 references component Bad Component not defined' in err
     assert 'Please specify the names of any component-definitions' in err
+
+
+def test_ssp_gen_and_assemble_more_than_one_param(tmp_trestle_dir: pathlib.Path, monkeypatch: MonkeyPatch) -> None:
+    """Test ssp generate and assemble with more than 1 parameters per rule."""
+    gen_args, _ = setup_for_ssp(tmp_trestle_dir, prof_name, ssp_name, False, '', 'comp_def_more_params')
+    args_compdefs = gen_args.compdefs
+
+    # first create the markdown
+    ssp_gen = SSPGenerate()
+    assert ssp_gen._run(gen_args) == 0
+    new_version = '1.2.3'
+
+    md_path = tmp_trestle_dir / ssp_name / 'ac' / 'ac-1.md'
+    assert md_path.exists()
+
+    md_api = MarkdownAPI()
+    header, tree = md_api.processor.process_markdown(md_path)
+    rule_parameters = header['x-trestle-comp-def-rules-param-vals']['comp_ca']
+    rule_parameters.append({'name': 'allowed_admins_per_account2', 'values': ['20']})
+
+    md_api.write_markdown_with_header(md_path, header, tree.content.raw_text)
+
+    # verifies a second parameter has beend added to the top shared rule
+    assert header['x-trestle-rules-params']['comp_ca'][1]['name'] == 'allowed_admins_per_account2'
+
+    # verifies the parameter value for the rule has been written down correctly in the markdown file
+    assert header['x-trestle-comp-def-rules-param-vals']['comp_ca'][1]['values'] == ['20']
+
+    # now assemble controls into json ssp
+    ssp_assemble = SSPAssemble()
+    args = argparse.Namespace(
+        trestle_root=tmp_trestle_dir,
+        markdown=ssp_name,
+        output=ssp_name,
+        verbose=0,
+        regenerate=False,
+        version=new_version,
+        name=None,
+        compdefs=args_compdefs
+    )
+    assert ssp_assemble._run(args) == 0
+
+    assem_ssp, _ = ModelUtils.load_model_for_class(tmp_trestle_dir, ssp_name, ossp.SystemSecurityPlan)
+    set_parameters = assem_ssp.control_implementation.implemented_requirements[0].by_components[0].set_parameters
+    set_params = [
+        set_param.param_id for set_param in set_parameters if set_param.param_id == 'allowed_admins_per_account2'
+    ]
+    # this demonstrates there's only one iteration of the parameter and not being repeated
+    assert len(set_params) == 1
+
+
+def test_ssp_gen_throw_exception_for_rep_comps(tmp_trestle_dir: pathlib.Path, monkeypatch: MonkeyPatch) -> None:
+    """Test ssp generate for duplicated component uuids between diff component definition."""
+    gen_args, _ = setup_for_ssp(tmp_trestle_dir, prof_name, ssp_name, False, '',
+                                'comp_def_more_params,comp_def_more_params_dup')
+    # first create the markdown
+    ssp_gen = SSPGenerate()
+    assert ssp_gen._run(gen_args) == 1
diff --git a/tests/trestle/tasks/csv_to_oscal_cd_test.py b/tests/trestle/tasks/csv_to_oscal_cd_test.py
index cabfc541..37286f5b 100644
--- a/tests/trestle/tasks/csv_to_oscal_cd_test.py
+++ b/tests/trestle/tasks/csv_to_oscal_cd_test.py
@@ -470,6 +470,158 @@ def test_execute_bp3_sample(tmp_path: pathlib.Path) -> None:
     assert ci.set_parameters[3].values[2] == '5'
 
 
+def test_execute_bp4_sample(tmp_path: pathlib.Path) -> None:
+    """Test execute bp4 sample."""
+    _, section = _get_config_section_init(tmp_path, 'test-csv-to-oscal-cd-bp.config')
+    section['csv-file'] = 'tests/data/csv/bp.sample.v4.csv'
+    # perform transformation from csv to OSCAL json
+    tgt = csv_to_oscal_cd.CsvToOscalComponentDefinition(section)
+    retval = tgt.execute()
+    assert retval == TaskOutcome.SUCCESS
+    # read component-definition
+    fp = pathlib.Path(tmp_path) / 'component-definition.json'
+    cd = ComponentDefinition.oscal_read(fp)
+    # spot check
+    component = cd.components[0]
+    assert len(component.props) == 68
+    assert len(component.control_implementations) == 1
+    # props
+    index = 0
+    assert component.props[index].name == 'Rule_Id'
+    assert component.props[index].value == 'account_owner_authorized_ip_range_configured'
+    assert component.props[index].remarks == 'rule_set_00'
+    index = 7
+    assert component.props[index].name == 'Parameter_Id'
+    assert component.props[index].value == 'allowed_admins_per_account'
+    assert component.props[index].remarks == 'rule_set_01'
+    index = 8
+    assert component.props[index].name == 'Parameter_Description'
+    assert component.props[index].value == 'Maximum allowed administrators per'
+    assert component.props[index].remarks == 'rule_set_01'
+    index = 9
+    assert component.props[index].name == 'Parameter_Value_Alternatives'
+    assert component.props[index].value == '10'
+    assert component.props[index].remarks == 'rule_set_01'
+    index = 10
+    assert component.props[index].name == 'Parameter_Id2'
+    assert component.props[index].value == 'allowed_admins_per_account2'
+    assert component.props[index].remarks == 'rule_set_01'
+    index = 11
+    assert component.props[index].name == 'Parameter_Description2'
+    assert component.props[index].value == 'Maximum allowed administrators per2'
+    assert component.props[index].remarks == 'rule_set_01'
+    index = 12
+    assert component.props[index].name == 'Parameter_Value_Alternatives2'
+    assert component.props[index].value == '20'
+    assert component.props[index].remarks == 'rule_set_01'
+    index = 13
+    assert component.props[index].name == 'Check_Id'
+    assert component.props[index].value == 'iam_admin_role_users_per_account_maxcount'
+    assert component.props[index].remarks == 'rule_set_01'
+    # control implementations
+    ci = component.control_implementations[0]
+    assert len(ci.set_parameters) == 8
+    index = 0
+    assert ci.set_parameters[index].param_id == 'allowed_admins_per_account'
+    assert len(ci.set_parameters[index].values) == 1
+    assert ci.set_parameters[index].values[0] == '10'
+    index = 1
+    assert ci.set_parameters[index].param_id == 'allowed_admins_per_account2'
+    assert len(ci.set_parameters[index].values) == 1
+    assert ci.set_parameters[index].values[0] == '20'
+    index = 2
+    assert ci.set_parameters[index].param_id == 'api_keys_rotated_days'
+    assert len(ci.set_parameters[index].values) == 3
+    assert ci.set_parameters[index].values[0] == 'x'
+    assert ci.set_parameters[index].values[1] == 'y'
+    assert ci.set_parameters[index].values[2] == 'z'
+    index = 3
+    assert ci.set_parameters[index].param_id == 'api_keys_rotated_days2'
+    assert len(ci.set_parameters[index].values) == 3
+    assert ci.set_parameters[index].values[0] == 'r'
+    assert ci.set_parameters[index].values[1] == 's'
+    assert ci.set_parameters[index].values[2] == 't'
+
+
+def test_execute_bp4_modify_additional_param_set(tmp_path: pathlib.Path) -> None:
+    """Test execute bp4 modify additional param set."""
+    _, section = _get_config_section_init(tmp_path, 'test-csv-to-oscal-cd-bp.config')
+    section['csv-file'] = 'tests/data/csv/bp.sample.v4.csv'
+    # modify additional param set
+    rows = _get_rows('tests/data/csv/bp.sample.v4.csv')
+    row = rows[3]
+    assert row[17] == 'allowed_admins_per_account2'
+    assert row[19] == '20'
+    assert row[20] == '20'
+    row[19] = '50'
+    row[20] = '45 50 55'
+    # perform transformation from csv to OSCAL json
+    with mock.patch('trestle.tasks.csv_to_oscal_cd.csv.reader') as mock_csv_reader:
+        mock_csv_reader.return_value = rows
+        tgt = csv_to_oscal_cd.CsvToOscalComponentDefinition(section)
+        retval = tgt.execute()
+        assert retval == TaskOutcome.SUCCESS
+    # read component-definition
+    fp = pathlib.Path(tmp_path) / 'component-definition.json'
+    cd = ComponentDefinition.oscal_read(fp)
+    # spot check
+    component = cd.components[0]
+    assert len(component.props) == 68
+    assert len(component.control_implementations) == 1
+    # props
+    index = 0
+    assert component.props[index].name == 'Rule_Id'
+    assert component.props[index].value == 'account_owner_authorized_ip_range_configured'
+    assert component.props[index].remarks == 'rule_set_00'
+    index = 12
+    assert component.props[index].name == 'Parameter_Value_Alternatives2'
+    assert component.props[index].value == '45 50 55'
+    assert component.props[index].remarks == 'rule_set_01'
+    # control implementations
+    ci = component.control_implementations[0]
+    assert len(ci.set_parameters) == 8
+    index = 0
+    assert ci.set_parameters[index].param_id == 'allowed_admins_per_account'
+    assert len(ci.set_parameters[index].values) == 1
+    assert ci.set_parameters[index].values[0] == '10'
+    index = 1
+    assert ci.set_parameters[index].param_id == 'allowed_admins_per_account2'
+    assert len(ci.set_parameters[index].values) == 1
+    assert ci.set_parameters[index].values[0] == '50'
+
+
+def test_execute_bp4_delete_additional_param_set(tmp_path: pathlib.Path) -> None:
+    """Test execute bp4 delete additional param set."""
+    _, section = _get_config_section_init(tmp_path, 'test-csv-to-oscal-cd-bp.config')
+    section['csv-file'] = 'tests/data/csv/bp.sample.v4.csv'
+    # delete additional param set
+    rows = _get_rows('tests/data/csv/bp.sample.v4.csv')
+    row = rows[3]
+    assert row[17] == 'allowed_admins_per_account2'
+    assert row[19] == '20'
+    assert row[20] == '20'
+    row[17] = ''
+    row[18] = ''
+    row[19] = ''
+    row[20] = ''
+    # perform transformation from csv to OSCAL json
+    with mock.patch('trestle.tasks.csv_to_oscal_cd.csv.reader') as mock_csv_reader:
+        mock_csv_reader.return_value = rows
+        tgt = csv_to_oscal_cd.CsvToOscalComponentDefinition(section)
+        retval = tgt.execute()
+        assert retval == TaskOutcome.SUCCESS
+    # read component-definition
+    fp = pathlib.Path(tmp_path) / 'component-definition.json'
+    cd = ComponentDefinition.oscal_read(fp)
+    # spot check
+    component = cd.components[0]
+    assert len(component.props) == 65
+    assert len(component.control_implementations) == 1
+    # control implementations
+    ci = component.control_implementations[0]
+    assert len(ci.set_parameters) == 7
+
+
 def test_execute_bp_cd(tmp_path: pathlib.Path) -> None:
     """Test execute bp cd."""
     _, section = _get_config_section_init(tmp_path, 'test-csv-to-oscal-cd-bp.config')