Skip to content

extend the 'has' relation in EST and JSON policy format#2154

Open
victornicolet wants to merge 5 commits intomainfrom
extended-has-in-est
Open

extend the 'has' relation in EST and JSON policy format#2154
victornicolet wants to merge 5 commits intomainfrom
extended-has-in-est

Conversation

@victornicolet
Copy link

@victornicolet victornicolet commented Feb 12, 2026

Description of changes

This add a new variant for the has operator in the EST, which translates to an extended has in JSON:

{"has": {"left": <EXPR>, "attr": ["attribute", "nested",...]}}

This is backwards compatible, in the sense that:

  • simple has i.e. {"has": {"left": <EXPR>, "attr": "attribute"}} still results in the same expression as before,
  • the extended has operator in the Cedar language is still desugared to a conjunction of has and . when converting to JSON.

We may want to validate that in the extended has, each element in the list is a valid identifier to match the extended has of the Cedar policy language.

The new EST results in changes in the exported types in cedar-wasm/pkg/*/cedar_wasm.d.ts. We have a new type:

+ export type HasAttrRepr = { left: Expr; attr: SmolStr } | { left: Expr; attr: NonEmpty<SmolStr> };

And the type of expressions uses this type:

export type ExprNoExt = { Value: CedarValueJson } 
| { Var: Var } | { Slot: string } | { "!": { arg: Expr } } 
| { neg: { arg: Expr } } | { "==": { left: Expr; right: Expr } } 
| { "!=": { left: Expr; right: Expr } } | { in: { left: Expr; right: Expr } } 
| { "<": { left: Expr; right: Expr } } | { "<=": { left: Expr; right: Expr } } 
| { ">": { left: Expr; right: Expr } } | { ">=": { left: Expr; right: Expr } }
 | { "&&": { left: Expr; right: Expr } } 
| { "||": { left: Expr; right: Expr } } 
| { "+": { left: Expr; right: Expr } } 
| { "-": { left: Expr; right: Expr } } 
| { "*": { left: Expr; right: Expr } } 
| { contains: { left: Expr; right: Expr } } 
| { containsAll: { left: Expr; right: Expr } } | { containsAny: { left: Expr; right: Expr } } 
| { isEmpty: { arg: Expr } } 
| { getTag: { left: Expr; right: Expr } } | { hasTag: { left: Expr; right: Expr } } | { ".": { left: Expr; attr: SmolStr } } 
+ | { has: HasAttrRepr } 
- | { has:  { left: Expr; attr: SmolStr } }
| { like: { left: Expr; pattern: PatternElem[] } } | { is: { left: Expr; entity_type: SmolStr; in?: Expr } } 
| { "if-then-else": { if: Expr; then: Expr; else: Expr } } | { Set: Expr[] } | { Record: Record<string, Expr> };

Typescript that was previously type-checking should typecheck with the new types.

Issue #, if available

#1889 only the EST extension part. Does not touch the AST.

Checklist for requesting a review

The change in this PR is (choose one, and delete the other options):

  • A backwards-compatible change requiring a minor version bump to cedar-policy (e.g., addition of a new API).

I confirm that this PR (choose one, and delete the other options):

  • Updates the "Unreleased" section of the CHANGELOG with a description of my change (required for major/minor version bumps).

I confirm that cedar-spec (choose one, and delete the other options):

  • Does not require updates because my change does not impact the Cedar formal model or DRT infrastructure.
  • Requires updates, and I have made / will make these updates myself. (Please include in your description a timeline or link to the relevant PR in cedar-spec, and how you have tested that your updates are correct.)
  • Requires updates, but I do not plan to make them in the near future. (Make sure that your changes are hidden behind a feature flag to mark them as experimental.)
  • I'm not sure how my change impacts cedar-spec. (Post your PR anyways, and we'll discuss in the comments.)

I confirm that docs.cedarpolicy.com (choose one, and delete the other options):

  • Requires updates, and I have made / will make these updates myself. (Please include in your description a timeline or link to the relevant PR in cedar-docs. PRs should be targeted at a staging-X.Y branch, not main.)

…or in conversions

Signed-off-by: Victor Nicolet <victornl@amazon.com>
Signed-off-by: Victor Nicolet <victornl@amazon.com>
@victornicolet
Copy link
Author

Looks like this breaks cedar-wasm because it exposes the EST.

Copy link
Contributor

@cdisselkoen cdisselkoen left a comment

Choose a reason for hiding this comment

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

Looks good, modulo the TODOs and the issue of cedar-wasm breakage

Simple {
/// Left-hand argument
left: Arc<Expr>,
/// Atribute tested
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
/// Atribute tested
/// Attribute tested

Comment on lines 1818 to 1827
// Extended has - serialize
let expr = Expr::ExprNoExt(ExprNoExt::HasAttr(HasAttrRepr::Extended {
left: Arc::new(Expr::ExprNoExt(ExprNoExt::Var(ast::Var::Context))),
attr: nonempty!["user".into(), "profile".into(), "email".into()],
}));
let json = serde_json::to_value(&expr).unwrap();
assert_eq!(
json,
serde_json::json!({"has": {"left": {"Var": "context"}, "attr": ["user", "profile", "email"]}})
);
Copy link
Contributor

Choose a reason for hiding this comment

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

Might be useful to have a test for an Extended with only one attr. (Obviously that could also be represented with Simple, but IIUC, in this PR we will allow the Extended representation with a singleton set as a legal JSON)

Copy link
Author

Choose a reason for hiding this comment

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

Yes, good point. I'll also add a test that shows deserializing "has": { ... "attr": []} fails (empty list).

Signed-off-by: Victor Nicolet <victornl@amazon.com>
@github-actions
Copy link

Coverage Report

Head Commit: 44f8979689fc611e30c5c783efdc86ef5e35f5dc

Base Commit: 332b7b810621b36b21988172afcbec56af93fecf

Download the full coverage report.

Coverage of Added or Modified Lines of Rust Code

Required coverage: 80.00%

Actual coverage: 57.58%

Status: FAILED ❌

Details
File Status Covered Coverage Missed Lines
cedar-policy-core/src/est/expr.rs 🔴 19/33 57.58% 839, 841-842, 844, 1428-1431, 1433, 1435, 1437-1438, 1440, 1443

Coverage of All Lines of Rust Code

Required coverage: 80.00%

Actual coverage: 86.27%

Status: PASSED ✅

Details
Package Status Covered Coverage Base Coverage
cedar-language-server 🟢 4723/5104 92.54% 92.54%
cedar-policy 🟡 3775/5138 73.47% 73.47%
cedar-policy-cli 🔴 785/1204 65.20% 65.20%
cedar-policy-core 🟢 22042/25330 87.02% 87.06%
cedar-policy-formatter 🟢 914/1088 84.01% 84.01%
cedar-policy-symcc 🟢 6656/7193 92.53% 92.53%
cedar-wasm 🔴 0/28 0.00% 0.00%

@victornicolet
Copy link
Author

victornicolet commented Feb 13, 2026

This results in a change in the exported types in cedar-wasm/pkg/*/cedar_wasm.d.ts.
Before:

export type ExprNoExt = ... | { has: { left: Expr; attr: SmolStr }} | ...

Now:

export type ExprNoExt = ... | { has: HasAttrRepr } | ...
export type HasAttrRepr = { left: Expr; attr: SmolStr } | { left: Expr; attr: NonEmpty<SmolStr> };

Expressions that typechecked before still typecheck after the change.

@cdisselkoen
Copy link
Contributor

Nice, way to go making the TS export a nonbreaking change

Signed-off-by: Victor Nicolet <victornl@amazon.com>
@github-actions
Copy link

Coverage Report

Head Commit: 36ac084befef2e9926340ed1375ce49438f2223f

Base Commit: 332b7b810621b36b21988172afcbec56af93fecf

Download the full coverage report.

Coverage of Added or Modified Lines of Rust Code

Required coverage: 80.00%

Actual coverage: 93.94%

Status: PASSED ✅

Details
File Status Covered Coverage Missed Lines
cedar-policy-core/src/est/expr.rs 🟢 31/33 93.94% 1433, 1440

Coverage of All Lines of Rust Code

Required coverage: 80.00%

Actual coverage: 86.30%

Status: PASSED ✅

Details
Package Status Covered Coverage Base Coverage
cedar-language-server 🟢 4723/5104 92.54% 92.54%
cedar-policy 🟡 3776/5138 73.49% 73.47%
cedar-policy-cli 🔴 785/1204 65.20% 65.20%
cedar-policy-core 🟢 22054/25330 87.07% 87.06%
cedar-policy-formatter 🟢 914/1088 84.01% 84.01%
cedar-policy-symcc 🟢 6656/7193 92.53% 92.53%
cedar-wasm 🔴 0/28 0.00% 0.00%

@github-actions
Copy link

Coverage Report

Head Commit: 1aa74bd9ba74d9b2c797400b17b05d0128c1cde1

Base Commit: 332b7b810621b36b21988172afcbec56af93fecf

Download the full coverage report.

Coverage of Added or Modified Lines of Rust Code

Required coverage: 80.00%

Actual coverage: 93.94%

Status: PASSED ✅

Details
File Status Covered Coverage Missed Lines
cedar-policy-core/src/est/expr.rs 🟢 31/33 93.94% 1433, 1440

Coverage of All Lines of Rust Code

Required coverage: 80.00%

Actual coverage: 86.30%

Status: PASSED ✅

Details
Package Status Covered Coverage Base Coverage
cedar-language-server 🟢 4723/5104 92.54% 92.54%
cedar-policy 🟡 3776/5138 73.49% 73.47%
cedar-policy-cli 🔴 785/1204 65.20% 65.20%
cedar-policy-core 🟢 22054/25330 87.07% 87.06%
cedar-policy-formatter 🟢 914/1088 84.01% 84.01%
cedar-policy-symcc 🟢 6656/7193 92.53% 92.53%
cedar-wasm 🔴 0/28 0.00% 0.00%

Arc::unwrap_or_clone(left).try_into_ast(id)?,
attr,
)),
// Should the try_into_ast use a builder anyway?
Copy link
Contributor

Choose a reason for hiding this comment

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

I believe try_into_ast could written entirely in terms of a generic ExprBuilder if that's what you're asking. For the moment, we only convert to AST so it doesn't matter, but we might want that for a future public-syntax tree.

write!(f, " has \"{}\"", attr.head.escape_debug())?;
}
for attr in attr.tail.iter() {
// TODO: validation, we shouldn't have non-idents here
Copy link
Contributor

Choose a reason for hiding this comment

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

I assume this is what you mean here, but it's worth commenting that principal has "foo".bar doesn't parse

Copy link
Author

Choose a reason for hiding this comment

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

Yes that's what I meant, I can add the comment.

Signed-off-by: Victor Nicolet <victornl@amazon.com>
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