Skip to content

Cedar/Rego: extract PolicyGenerator trait and collapse parallel *_stub fields (Phase B) #71

@boorad

Description

@boorad

Summary

Phase B of #27 — clean up the abstraction now that Phase A has shipped two
policy engines side-by-side.

Phase A (#27, landed in feat: add Cedar as a second policy engine,
db5b843) deliberately duplicated structure to stay backward-compatible:

  • Finding::rego_stub and Finding::cedar_stub are parallel optional fields
  • extract_rego / extract_cedar are parallel functions in
    src/commands/extract.rs with engine-specific dispatch from
    match args.engine { ... }
  • MCP suggest_policy and validate_policy match engine { Rego => ..., Cedar => ... } over each branch of the call
  • [rule.rego_template] and [rule.cedar_template] are parallel TOML blocks

That's the right shape for two engines. It will not scale to N engines without
becoming a nuisance.

Scope

1. Extract a PolicyGenerator trait

pub trait PolicyGenerator {
    fn engine(&self) -> PolicyEngine;
    fn validate(&self, policy: &str) -> ValidationResult;
    fn wrap_by_confidence(&self, body: &str, confidence: Confidence) -> String;
    fn group_and_generate(&self, findings: &[Finding], opts: &ExtractOpts) -> Vec<PolicyFile>;
}

RegoGenerator and CedarGenerator implement it. extract::execute becomes
generic over dyn PolicyGenerator, dispatched off the --engine flag at one
point of entry. Same refactor for the MCP tools (suggest_policy /
validate_policy collapse onto the trait).

2. Collapse parallel *_stub fields into policy_outputs

Replace Finding::rego_stub and Finding::cedar_stub with:

pub struct PolicyOutput {
    pub engine: PolicyEngine,
    pub content: String,
}

pub struct Finding {
    // ...
    pub policy_outputs: Vec<PolicyOutput>,
}

Backward compatibility is part of this work item, not a separate gate.
Ship a serde deserialization shim that reads legacy rego_stub /
cedar_stub fields and folds them into policy_outputs so existing
findings files keep loading. Output continues to populate policy_outputs
only — old fields are write-removed but read-tolerated.

3. Same treatment for the rule TOML format

Replace [rule.rego_template] and [rule.cedar_template] with a single
[[rule.policy_templates]] array:

[[rule.policy_templates]]
engine = "rego"
template = """..."""

[[rule.policy_templates]]
engine = "cedar"
template = """..."""

Same migration story — accept both shapes during parse, fold the legacy
keys into the new structure.

Out of scope

  • New engines (Phase B is a refactor, not a feature add)
  • Auto-translation between engines
  • Cedar runtime / enforcement
  • Engine-selection heuristics

Risks

  • MCP tool-name churn. suggest_rego / validate_rego aliases stay; this
    refactor is internal.
  • Output-shape change for downstream JSON consumers. Anything reading
    rego_stub / cedar_stub off zift's stdout will need to read
    policy_outputs instead. Document explicitly in the changelog. The input
    shim handles persisted files; it does not paper over consumer code.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions