Skip to content

fix: validate profile imports against their own catalogs (oscal-cli#250)#282

Merged
david-waltermire merged 2 commits into
metaschema-framework:developfrom
david-waltermire:fix/issue-250-multi-import
Apr 23, 2026
Merged

fix: validate profile imports against their own catalogs (oscal-cli#250)#282
david-waltermire merged 2 commits into
metaschema-framework:developfrom
david-waltermire:fix/issue-250-multi-import

Conversation

@david-waltermire
Copy link
Copy Markdown
Contributor

@david-waltermire david-waltermire commented Apr 23, 2026

Closes metaschema-framework/oscal-cli#250

Summary

Fixes metaschema-framework/oscal-cli#250: when a profile imports two or more catalogs and uses with-ids rather than include-all, validation incorrectly reports control identifiers on the second (and later) imports as missing.

Root cause

In src/main/metaschema-constraints/oscal-external-constraints.xml, the /profile/import context defined:

<index id="oscal-profile-import-index-control-id"
       name="profile-import-index-control-id"
       target="$resolved-profile-import//control">
    <key-field target="@id"/>
</index>
<index-has-key id="oscal-profile-import-has-key-include-exclude-control-id"
               name="profile-import-index-control-id"
               target="(include-controls|exclude-controls)/with-id">
    <key-field target="."/>
</index-has-key>

Metaschema index names are document-global. With two imports, both instances of the constraint try to build an index with the same name — the validator emits a "Duplicate index" finding on the second import, the second index is discarded, and subsequent with-id lookups resolve against the first import's catalog only. Every id from the second import's catalog is then reported missing.

Fix

Replace the index/index-has-key pair with an inline expect that uses the per-import $resolved-profile-import let binding to perform the lookup directly:

<expect id="oscal-profile-import-include-exclude-control-id-in-imported-catalog"
        target="(include-controls|exclude-controls)/with-id"
        test="let $id := . return exists($resolved-profile-import//control[@id = $id])">
    ...
</expect>

Each import evaluates its test against the catalog resolved for the same import, with no shared global state.

Test plan

  • New regression test OscalValidationTest#testValidateProfileWithMultipleImports — builds a two-import profile (one import uses the build-downloaded NIST 800-53 rev5 catalog at target/download/content/NIST_SP-800-53_rev5_catalog.json, the other a minimal local catalog with a single test-01 control). Fails on pre-fix, passes on post-fix.
  • Existing OscalValidationTest#testValidateProfileWithMissingControls still correctly reports invalid control ids — the new expect replaces the missing-control detection end-to-end.
  • mvn test — 47 tests run, 0 failures, 0 errors, 3 skipped.

Summary by CodeRabbit

  • Bug Fixes

    • Validation for profiles importing multiple catalogs now checks that referenced control IDs exist in the imported catalogs and reports a clear error when an ID is missing.
  • Tests

    • Added regression tests covering multi-catalog imports (including reversed import order) and new test fixtures to ensure consistent validation behavior.

The oscal-profile-import-has-key-include-exclude-control-id constraint
used a document-global named index (profile-import-index-control-id)
built from each profile import's resolved catalog. With more than one
import the index name collides, so only the first import's index is
consulted when checking with-id entries on later imports, causing valid
references to be reported as missing.

Replace the index/index-has-key pair with an inline expect that uses
the per-context $resolved-profile-import let binding to look up the
referenced control id directly in the catalog resolved for the same
import.

Adds a regression test that validates a profile importing two separate
catalogs (NIST 800-53 rev5 from the downloaded test content plus a
local test catalog); previously failed on the second import, now passes.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 23, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 22cc2526-9839-4073-80e4-0d939ee4b6f9

📥 Commits

Reviewing files that changed from the base of the PR and between 93fde59 and 37d0871.

📒 Files selected for processing (4)
  • src/test/java/dev/metaschema/oscal/lib/validation/OscalValidationTest.java
  • src/test/resources/content/issue-250/test-catalog-ac-1.json
  • src/test/resources/content/issue-250/test-profile-reversed.json
  • src/test/resources/content/issue-250/test-profile.json
✅ Files skipped from review due to trivial changes (3)
  • src/test/resources/content/issue-250/test-catalog-ac-1.json
  • src/test/resources/content/issue-250/test-profile-reversed.json
  • src/test/resources/content/issue-250/test-profile.json

📝 Walkthrough

Walkthrough

Replaces index/key-field based validation for profile imports with an predicate that asserts the referenced control ID exists in the resolved imported catalog, and adds tests and fixtures validating profiles that import two catalogs (including reversed import order).

Changes

Cohort / File(s) Summary
Constraint Definition
src/main/metaschema-constraints/oscal-external-constraints.xml
Removed index/key-field constraints for import control-id validation and added a single <expect> (oscal-profile-import-include-exclude-control-id-in-imported-catalog) that checks existence of a resolved imported-catalog control by @id.
Validation Tests
src/test/java/dev/metaschema/oscal/lib/validation/OscalValidationTest.java
Added two regression tests (testValidateProfileWithMultipleImports, testValidateProfileWithMultipleImportsReversed) and a helper to run validation and log issues when failing.
Test Fixtures
src/test/resources/content/issue-250/test-profile.json, src/test/resources/content/issue-250/test-profile-reversed.json, src/test/resources/content/issue-250/test-catalog.json, src/test/resources/content/issue-250/test-catalog-ac-1.json
Added JSON fixtures: two catalogs (test-catalog.json, test-catalog-ac-1.json) each with a control, and two profiles importing them in different orders to exercise the multi-import validation scenario.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Suggested reviewers

  • aj-stein

Poem

🐰 I hopped through XML, tests in tow,
I checked each import, high and low,
No cross-wired indexes now in sight,
Every control ID stands upright,
Hooray — validation done just right! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title clearly identifies the fix (validation bug) and references the related issue (oscal-cli#250), directly matching the changeset's main objective.
Linked Issues check ✅ Passed The PR fixes the documented bug where profiles importing two catalogs incorrectly validated controls against only the first catalog by replacing a global index with per-import expect predicates.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the multi-import validation bug and adding regression tests; no unrelated code modifications are present.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/test/resources/content/issue-250/test-profile.json (1)

12-12: Prefer a fully local fixture path for deterministic test execution.

Using target/download/... couples the regression test to build-side artifact provisioning. Consider replacing this with a local minimal catalog fixture to keep the test hermetic across environments.

♻️ Suggested change
-        "href": "../../../../../target/download/content/NIST_SP-800-53_rev5_catalog.json",
+        "href": "test-catalog-ac-1.json",

Then add src/test/resources/content/issue-250/test-catalog-ac-1.json containing only the ac-1 control needed by this test.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/test/resources/content/issue-250/test-profile.json` at line 12, Replace
the external build-dependent href in test-profile.json (the "href":
"../../../../../target/download/content/NIST_SP-800-53_rev5_catalog.json" entry)
with a deterministic local fixture path and add the suggested minimal catalog
file; specifically change the href to
"src/test/resources/content/issue-250/test-catalog-ac-1.json" in
test-profile.json and create
src/test/resources/content/issue-250/test-catalog-ac-1.json containing only the
AC-1 control required by the test so the test becomes hermetic and does not
depend on target/download artifacts.
src/test/java/dev/metaschema/oscal/lib/validation/OscalValidationTest.java (1)

115-134: Add a reversed-import-order variant to lock in the original failure mode.

Nice regression coverage. Consider adding a companion case with the two imports reversed, since order sensitivity was part of the reported symptom.

✅ Small extension to strengthen regression coverage
   `@Test`
   void testValidateProfileWithMultipleImports()
       throws MetaschemaException, IOException, URISyntaxException, ConstraintValidationException {
@@
     assertTrue(validationResult.isPassing());
   }
+
+  `@Test`
+  void testValidateProfileWithMultipleImportsReversedOrder()
+      throws MetaschemaException, IOException, URISyntaxException, ConstraintValidationException {
+    IBindingContext bindingContext = OscalBindingContext.newInstance();
+    IValidationResult validationResult = bindingContext.validateWithConstraints(
+        Paths.get("src/test/resources/content/issue-250/test-profile-reversed.json").toUri(),
+        null);
+    assertTrue(validationResult.isPassing());
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/test/java/dev/metaschema/oscal/lib/validation/OscalValidationTest.java`
around lines 115 - 134, Add a companion test to lock in the original failure:
create a new test method (e.g., testValidateProfileWithMultipleImportsReversed)
in OscalValidationTest that mirrors testValidateProfileWithMultipleImports but
validates the profile whose import order is reversed, using the same
IBindingContext (OscalBindingContext.newInstance()) and validateWithConstraints
call, then assert the validationResult is passing; ensure you add the
reversed-import test resource (same catalogs but imports swapped) and reuse the
same LoggingValidationHandler/assertTrue checks so the regression is caught if
import-order sensitivity reappears.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/test/java/dev/metaschema/oscal/lib/validation/OscalValidationTest.java`:
- Around line 115-134: Add a companion test to lock in the original failure:
create a new test method (e.g., testValidateProfileWithMultipleImportsReversed)
in OscalValidationTest that mirrors testValidateProfileWithMultipleImports but
validates the profile whose import order is reversed, using the same
IBindingContext (OscalBindingContext.newInstance()) and validateWithConstraints
call, then assert the validationResult is passing; ensure you add the
reversed-import test resource (same catalogs but imports swapped) and reuse the
same LoggingValidationHandler/assertTrue checks so the regression is caught if
import-order sensitivity reappears.

In `@src/test/resources/content/issue-250/test-profile.json`:
- Line 12: Replace the external build-dependent href in test-profile.json (the
"href":
"../../../../../target/download/content/NIST_SP-800-53_rev5_catalog.json" entry)
with a deterministic local fixture path and add the suggested minimal catalog
file; specifically change the href to
"src/test/resources/content/issue-250/test-catalog-ac-1.json" in
test-profile.json and create
src/test/resources/content/issue-250/test-catalog-ac-1.json containing only the
AC-1 control required by the test so the test becomes hermetic and does not
depend on target/download artifacts.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: eb651609-fd7d-4796-8e40-16b85a8a3046

📥 Commits

Reviewing files that changed from the base of the PR and between 088a7a7 and 93fde59.

📒 Files selected for processing (4)
  • src/main/metaschema-constraints/oscal-external-constraints.xml
  • src/test/java/dev/metaschema/oscal/lib/validation/OscalValidationTest.java
  • src/test/resources/content/issue-250/test-catalog.json
  • src/test/resources/content/issue-250/test-profile.json

Replaces the build-download NIST catalog dependency with a minimal
local AC-1 fixture so the test is independent of the download-maven
plugin running during test-compile and works regardless of build phase
ordering.

Adds testValidateProfileWithMultipleImportsReversed to exercise the
exact pre-fix asymmetry described in the original bug report (errors
followed whichever catalog was imported second), extracting a shared
assertMultiImportProfileValidates helper.
@david-waltermire david-waltermire merged commit c7dede7 into metaschema-framework:develop Apr 23, 2026
5 checks passed
@david-waltermire david-waltermire deleted the fix/issue-250-multi-import branch April 23, 2026 06:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant