The get_rule_details() function now automatically discovers and includes rendered content from build artifacts alongside the source rule information. This provides a unified view of both the source templates and the final rendered output in a single API call.
✅ Automatic Build Detection - Intelligently detects if build artifacts exist ✅ Unified Response - Single call returns both source and rendered content ✅ Multi-Product Support - Includes rendered content for all built products ✅ Product Filtering - Optionally filter to specific product builds ✅ Backward Compatible - Existing code continues to work ✅ Performance Optimized - Only queries builds when requested
# Old way: Two separate calls
rule = discovery.get_rule_details("my_rule") # Source only
rendered = discovery.get_rendered_rule("rhel9", "my_rule") # Build artifact# New way: One call, automatic detection
rule = discovery.get_rule_details("my_rule") # Source + all rendered versions!
# Access source
print(rule.title)
print(rule.description)
# Access rendered content for each built product
if rule.rendered:
for product, rendered in rule.rendered.items():
print(f"{product}: {rendered.rendered_yaml}")
print(f"Remediations: {rendered.rendered_remediations}")def get_rule_details(
rule_id: str,
include_rendered: bool = True,
product: Optional[str] = None
) -> Optional[RuleDetails]| Parameter | Type | Default | Description |
|---|---|---|---|
rule_id |
str | required | Rule identifier (e.g., "sshd_set_idle_timeout") |
include_rendered |
bool | True |
Whether to automatically include rendered content from builds |
product |
str | None |
Optional product filter. If specified, only includes rendered content for this product |
Returns a RuleDetails object with:
Source Information (always present):
rule_id,title,description,rationale,severityidentifiers,references,products,platformsremediations(availability flags),checks,test_scenariosfile_path,rule_dir,last_modified,template
Rendered Information (when builds exist):
rendered: Dict[str, RuleRenderedContent] - Keyed by productproduct: Product identifierrendered_yaml: Fully rendered YAML (variables expanded)rendered_oval: Rendered OVAL checkrendered_remediations: Dict of rendered scripts by typebuild_path: Path to build artifactsbuild_time: When the build was created
from content_agent.core import discovery
# Get rule with automatic rendered content detection
rule = discovery.get_rule_details("accounts_password_pam_dcredit")
print(f"Rule: {rule.title}")
print(f"Severity: {rule.severity}")
print(f"Source file: {rule.file_path}")
# Check if builds are available
if rule.rendered:
print(f"\nRendered content available for {len(rule.rendered)} products:")
for product, rendered in rule.rendered.items():
print(f"\n{product}:")
print(f" - Built: {rendered.build_time}")
print(f" - YAML size: {len(rendered.rendered_yaml)} chars")
print(f" - Has OVAL: {rendered.rendered_oval is not None}")
print(f" - Remediations: {list(rendered.rendered_remediations.keys())}")
else:
print("\nNo build artifacts found")Output:
Rule: Ensure PAM Enforces Password Requirements - Minimum Digit Characters
Severity: medium
Source file: linux_os/guide/.../accounts_password_pam_dcredit/rule.yml
Rendered content available for 2 products:
rhel10:
- Built: 2026-01-19 17:25:56
- YAML size: 5710 chars
- Has OVAL: True
- Remediations: ['bash', 'ansible']
rhel9:
- Built: 2026-01-19 17:25:59
- YAML size: 5698 chars
- Has OVAL: True
- Remediations: ['bash', 'ansible']
# Only get rendered content for RHEL 10
rule = discovery.get_rule_details("my_rule", product="rhel10")
if rule.rendered and "rhel10" in rule.rendered:
rendered = rule.rendered["rhel10"]
print(rendered.rendered_yaml)
print(rendered.rendered_remediations["bash"])# Skip build detection for faster response
rule = discovery.get_rule_details("my_rule", include_rendered=False)
# rule.rendered will be None
assert rule.rendered is Noneimport yaml
rule = discovery.get_rule_details("my_rule")
# Source description (may have Jinja templates)
print("Source:", rule.description)
# Rendered description (variables expanded)
if rule.rendered:
first_product = list(rule.rendered.keys())[0]
rendered_data = yaml.safe_load(rule.rendered[first_product].rendered_yaml)
print("Rendered:", rendered_data['description'])rule = discovery.get_rule_details("my_rule")
if rule.rendered and "rhel9" in rule.rendered:
remediations = rule.rendered["rhel9"].rendered_remediations
if "bash" in remediations:
print("Bash remediation for RHEL 9:")
print(remediations["bash"])
if "ansible" in remediations:
print("\nAnsible remediation for RHEL 9:")
print(remediations["ansible"])The MCP tool get_rule_details automatically uses this unified approach:
User: "Show me details about sshd_set_idle_timeout"
Claude: [calls get_rule_details(rule_id="sshd_set_idle_timeout")]
Response includes:
- Source rule information
- Rendered content for rhel9, rhel10 (if builds exist)
- OVAL checks with expanded variables
- Bash and Ansible remediations
Optional parameters:
Claude: [calls get_rule_details(
rule_id="my_rule",
include_rendered=true,
product="rhel9"
)]
- Before: 2+ calls (get_rule_details + get_rendered_rule for each product)
- After: 1 call gets everything
- No need to check if builds exist first
- No need to know which products are built
- Gracefully handles missing builds
- See source templates AND final output
- Compare Jinja variables vs expanded values
- Understand product-specific differences
- Get all products or filter to one
- Include/exclude rendered content as needed
- Maintain backward compatibility
- Rendered content only fetched when requested
- Product filtering reduces overhead
- Cached build detection
Scenario: Understand what a rule does and how it's implemented.
rule = discovery.get_rule_details("my_rule")
# Source information
print("What:", rule.title)
print("Why:", rule.rationale)
print("Severity:", rule.severity)
# Implementation
print("Template:", rule.template)
print("Source remediations:", rule.remediations)
# Actual rendered content
if rule.rendered:
for product, rendered in rule.rendered.items():
print(f"\n{product} implementation:")
print(rendered.rendered_remediations.get("bash", "N/A"))Scenario: Debug why Jinja templates aren't rendering as expected.
rule = discovery.get_rule_details("my_rule")
# Check source has template variables
if "{{{" in rule.description:
print("Source has Jinja variables")
# Check if they were expanded in builds
if rule.rendered:
for product, rendered in rule.rendered.items():
rendered_desc = yaml.safe_load(rendered.rendered_yaml)['description']
if "{{{" not in rendered_desc:
print(f"✅ {product}: Variables expanded")
else:
print(f"❌ {product}: Variables NOT expanded")Scenario: See how the same rule differs across products.
rule = discovery.get_rule_details("my_rule")
if rule.rendered:
print("Comparing implementations across products:\n")
for product, rendered in rule.rendered.items():
bash_script = rendered.rendered_remediations.get("bash", "")
print(f"{product}: {len(bash_script)} bytes")
# Check for product-specific code
if product in bash_script:
print(f" → Has {product}-specific code")Scenario: Verify builds produced expected output.
rule = discovery.get_rule_details("my_new_rule")
if not rule.rendered:
print("❌ Rule not in any builds - needs to be built")
elif "rhel9" not in rule.rendered:
print("❌ Rule not in RHEL 9 build")
else:
rendered = rule.rendered["rhel9"]
if rendered.rendered_oval:
print("✅ OVAL check generated")
if "bash" in rendered.rendered_remediations:
print("✅ Bash remediation generated")- Only checks if build directories exist
- No file I/O until rendered content is accessed
- Cached product list
# Fast: No build artifact access
rule = discovery.get_rule_details("my_rule", include_rendered=False)
# Medium: Check builds but limit to one product
rule = discovery.get_rule_details("my_rule", product="rhel9")
# Slower: Get all products (but still <1s for most rules)
rule = discovery.get_rule_details("my_rule")include_rendered=False: ~50msproduct="rhel9": ~100ms- All products (2 builds): ~150ms
{
# Source information (always present)
"rule_id": "my_rule",
"title": "My Rule Title",
"description": "Config value: {{{ xccdf_value('my_var') }}}", # May have Jinja
"severity": "medium",
"products": ["rhel9", "rhel10"],
"remediations": {"bash": True, "ansible": True}, # Availability flags
"file_path": "path/to/rule.yml",
# Rendered content (when builds exist)
"rendered": {
"rhel9": {
"product": "rhel9",
"rendered_yaml": "description: Config value: 300\n...", # Expanded
"rendered_oval": "<def-group>...</def-group>",
"rendered_remediations": {
"bash": "#!/bin/bash\n...",
"ansible": "---\n..."
},
"build_path": "build/rhel9/rules",
"build_time": "2026-01-19T17:25:59"
},
"rhel10": {
"product": "rhel10",
# ... same structure ...
}
}
}# Old code continues to work
rule = discovery.get_rule_details("my_rule")
print(rule.title)
print(rule.severity)
# No changes needed!# Add rendered content access
rule = discovery.get_rule_details("my_rule")
# Old fields still work
print(rule.title)
# New fields available
if rule.rendered:
print("Builds:", list(rule.rendered.keys()))# If you don't need rendered content, disable it
rule = discovery.get_rule_details("my_rule", include_rendered=False)
# Faster, smaller responseThe unified rule discovery feature provides:
✅ One call instead of multiple ✅ Automatic detection of build artifacts ✅ Complete information - source + rendered ✅ Flexible filtering by product ✅ Backward compatible with existing code ✅ Performance optimized with selective inclusion
This makes it much easier to:
- Investigate rules
- Debug templates
- Compare products
- Verify builds
- Understand final output
Try it now:
rule = discovery.get_rule_details("your_rule_id")