Skip to content

feat(explain): add explanation report types and settings - #876

Merged
Anand Krishnamoorthi (anakrish) merged 1 commit into
microsoft:mainfrom
snehabandla:explain/report-types
Oct 29, 2026
Merged

Anand Krishnamoorthi (anakrish) merged 1 commit into
microsoft:mainfrom
snehabandla:explain/report-types

Conversation

@snehabandla

Copy link
Copy Markdown
Contributor

Summary

This PR adds the typed contract shared by explanation capture, partial evaluation and the bindings. It is behind the default-off explanations feature and has no runtime behaviour yet: it defines types, settings, errors, serialization, and fixtures for downstream unit tests.

  • Reports (ExplanationReport):
    • One RuleExplanation per rule evaluation.
    • Each has its emissions (one per contribution, with partial-object key) and evaluated definition bodies (index + body for else branches and query blocks).
    • Conditions carry kind, outcome (success/failure/undefined/assumed), operator, operands and LoopWitness (saturating counters, outcome-matching sample).
  • Partial evaluation (PartialEvalResult):
    • ResidualQuery { value, conditions } alternatives. Complete rules and functions combine them as ordered first-match; partial sets combine them as a union.
    • Inexpressible residuals are empty with warnings, meaning unknown rather than undefined.
    • ResidualCondition is normalized to input_path op value, with ResidualKind::Truthy for not input.x.
    • Residual consumers must fail closed on an unknown kind.
  • Redaction (ReportedValue { Known(Value), Redacted }):
    • A withheld value is a variant, not a placeholder. It serializes as no value plus redacted: true (operands) or result_redacted/key_redacted/value_redacted: true (dependency rules and emissions).
    • query_result and the queried rule's top-level result are never redacted, because the caller receives them anyway.
    • Paths and condition text are policy source. PE results are exempt.
  • Settings: ExplanationSettings { value_mode, condition_mode, scope }, defaulting to Redacted / PrimaryOnly / All.
  • Errors: ExplainError (NotEnabled, EmissionIndexOutOfRange, Vm(VmError)).

Compatibility

  • All public types are #[non_exhaustive], and enums serialize in snake_case (Operator as Rego syntax).
  • Report consumers should accept unknown fields and values.
  • An undefined value is omitted, so it stays distinct from false, null and "<undefined>".
  • Sets serialize as arrays (existing Value serialization).
  • Reports are complete by construction. A truncation signal will be added with bounded tracing.

Testing

All runs used Rust 1.95.0.

  • 15 unit tests (--features explanations):
    • Exact JSON for 4 report fixtures, 3 PE fixtures, and the operand and redaction forms.
    • Undefined vs false vs null vs "<undefined>" in 9 value positions.
    • An exhaustive variant→name check for every enum and ReportedValue.
    • Operator Display, From<RuleType>, default settings, and error messages.
  • cargo fmt --check passes.
  • cargo clippy --all-features --all-targets -D warnings passes.
  • cargo clippy --no-default-features -- -D warnings passes. The separate --all-targets run has pre-existing bench failures unchanged from main.
  • Lib tests pass with the feature on (354) and off (339).
  • cargo doc gives 0 warnings in src/explain.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Residual queries can produce incorrect authorization results for conflicting definitions and omit the rule-kind information needed to distinguish complete-rule values from partial-set results.

2 open issues
What changed in this PR

Adds the shared explanation and partial-evaluation contract behind the default-off explanations feature, preparing the RVM and language bindings for future capture and reporting without adding runtime behavior.

Changes:

  • Defines report and residual types with explicit redaction and undefined-value serialization.
  • Adds explanation settings and typed errors.
  • Adds reusable fixtures and tests for serialization, defaults, enum names, and error messages.
File Description
src/​explain/​tests.rs Tests serialization and public contract behavior.
src/​explain/​settings.rs Defines value, condition, and reporting-scope settings.
src/​explain/​report.rs Defines explanation reports, residuals, and serialization.
src/​explain/​mod.rs Exports the API and documents compatibility rules.
src/​explain/​fixtures.rs Provides report and partial-evaluation fixtures.
src/​explain/​error.rs Defines explanation errors and wraps VM errors.

Review effort: Balanced

Comment thread src/explain/report.rs Resolved
Comment thread src/explain/report.rs Resolved
Add the typed contract shared by explanation capture, reporting and
partial evaluation, behind the default-off `explanations` feature. It has
no runtime behaviour yet; producers and consumers land separately.

- `ExplanationReport` with per-evaluation `RuleExplanation`s, their
  emissions, evaluated definition bodies, conditions, operands and loop
  witnesses.
- `PartialEvalResult` with `ResidualQuery` alternatives (with a `key`
  for partial objects) and structured `ResidualCondition`s. Its `kind`
  (`RuleKind`) says how the alternatives combine and how an empty outcome
  reads: undefined, an empty set or an empty object. Its mandatory
  `status` (`ResidualStatus`) is `exact` only when the alternatives
  describe the outcome; independent definitions that might conflict, or
  residuals JSON can't express, give `possible_conflict` or
  `unsupported` with no alternatives. Residual consumers fail closed on
  any other status and on an unknown kind.
- `ReportedValue { Known, Redacted }` so a withheld runtime value is not
  representable as a placeholder. Redacted values serialize as a
  `redacted`/`*_redacted: true` flag with no value.
- `ExplanationSettings` (`ValueMode`, `ConditionMode`,
  `ExplanationScope`) and the `ExplainError` enum.
- All public types are `#[non_exhaustive]` and serialize with snake_case
  names; undefined values are omitted so they stay distinct from `false`,
  `null` and the string "<undefined>".
- `cfg(test)` fixtures and exact-JSON tests for downstream unit tests.

Reports are complete by construction; a truncation signal will be added
with bounded tracing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

The in exception permits nested values whose JSON serialization changes Rego semantics, and the reporting contract incorrectly describes defined empty partial-rule collections as undefined.

2 open issues
2 resolved since last review

Review effort: Balanced

Comment thread src/explain/report.rs
Comment on lines +96 to +100
/// Final value of the rule. When no definition succeeded, this is the
/// rule's default value, if it has one. It is undefined when no
/// definition succeeded and there is no default, or when complete
/// definitions conflict (the default is not used then). Omitted from
/// JSON when undefined.
Comment thread src/explain/report.rs
Comment on lines +449 to +452
/// JSON can't distinguish a set from an array, or a non-string object key
/// from a string, so producers return [`ResidualStatus::Unsupported`] when a
/// residual key or value, or a condition value other than the collection of
/// an `in` test, contains a set or an object with non-string keys.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM
snehabandla The remaining issues can be addressed in next PR.

@anakrish
Anand Krishnamoorthi (anakrish) merged commit 64bb060 into microsoft:main Oct 29, 2026
60 checks passed
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.

3 participants