feat: expose type-aware partial evaluation (TPE) as tpe_authorize - #109
Draft
h0rv wants to merge 3 commits into
Draft
feat: expose type-aware partial evaluation (TPE) as tpe_authorize#109h0rv wants to merge 3 commits into
h0rv wants to merge 3 commits into
Conversation
Cargo.toml already allows this (caret on 4.8.2); only the lock held it back. No source changes were needed. Transitive churn is Cedar's own: jiff/jiff-tzdb/jiff-tzdb-platform back the datetime extension added upstream, and bs58, itertools, syn 3, rustc-literal-escaper, portable-atomic-util and windows-sys 0.61.2 come with it. beef, lazy_static, rustc_lexer and rustc_version drop out.
Adds policies_to_pst, which parses policy text into cedarpy.pst nodes: frozen dataclasses, one per cedar_policy::pst node kind. A consumer pattern-matches on real types instead of walking a dict and matching string keys. Rust walks pst::Expr once (matching what to_json_str already parses, just to a different target) and constructs the corresponding dataclass directly by calling its constructor from Rust. No JSON in between, no second Rust converter: the typed tree is the only thing Rust builds. dataclasses.asdict() and json.dumps() work on the result for free, since the nodes are plain dataclasses. Several pst types are #[non_exhaustive] upstream, so the matches on them keep a wildcard arm regardless. It raises ValueError naming the variant rather than building something silently wrong. Static policies and unlinked templates only. A residual from is_authorized_partial cannot convert this way: PST's own policy type rejects any clause containing an unresolved unknown(...) node, and every non-trivial residual has one. Confirmed by calling to_pst() on real residuals, not from the changelog. is_authorized_partial and its residuals field are untouched. Closes part of k9securityio#107.
Adds tpe_authorize for a request whose principal and/or resource identity is unknown but whose type is known. Separate from is_authorized_partial, which it does not call, change, or share a response shape with. TPE's residuals are checked against the schema and convert to PST; is_authorized_partial's do not, for the reason the previous commit found. principal/resource accept Type::"id" or a bare Type, since TPE's PartialEntityUid needs a type even when the id is unknown. action must be concrete. schema is required. entities must be fully concrete. The response is a TpeAuthzResult dataclass. permits/forbids stay separate, each a TpeClassification of residual/true/false/error policy ids. residual_policies maps id to a cedarpy.pst.Template, the same typed node the previous commit exposes, not JSON. Reuses its PstClasses/build_template rather than a second converter. Enables the tpe Cargo feature. Checked the resolved lockfile: no change, since neither crate's tpe feature has a dependency edge. Every failure raises ValueError: unlike is_authorized/ is_authorized_partial, there is no decision to fall back to when the input itself cannot be resolved. Closes the rest of k9securityio#107.
h0rv
force-pushed
the
feat/tpe-authorize
branch
from
August 21, 2026 19:01
b4b516d to
409d6b6
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Draft, stacked on #108. Read only the last commit,
409d6b6.The branch carries #108's commit
02e359funderneath, because GitHub cannot base a pull request on a branch that lives on a fork. #108 merges first, then this rebases onto main and02e359fdrops out, leaving only the TPE change. Transitively includes and is blocked by #106.What
Adds
tpe_authorize, an entry point for type-aware partial evaluation (TPE). Use it when you know the type of the principal or resource but not which one, e.g. someUser, not sure which, instead of a concreteUser::"alice".This is separate from
is_authorized_partial. It does not call it, change it, or share its response shape.is_authorized_partialandresidualsare untouched. The two partial-eval implementations in cedar-policy behave differently in a way that matters here.is_authorized_partial's residuals can contain untypedunknown(...)nodes that PST cannot represent, per #108 and #107. TPE's residuals are checked against the schema and do convert. Confirmed by callingto_pst()on real output from both paths.principalandresourceeach acceptType::"id"(concrete) or a bareType(known type, unknown id). TPE'sPartialEntityUidneeds a type even when the id is unknown, sois_authorized_partial's convention ofNonemeaning wholly unknown does not carry enough information here.actionmust be concrete.schemais required, unlikeis_authorized/is_authorized_partialwhere it is optional, because TPE builds its request and entity types against it.entitiesmust be fully concrete; this does not expose TPE's on-demand entity loading.The response is a
TpeAuthzResultdataclass.permitsandforbidsstay separate, each aTpeClassificationofresidual_ids,true_ids,false_ids,error_ids.residual_policiesmaps policy id to acedarpy.pst.Template, the same typed node #108 exposes, not JSON.Every failure raises
ValueError. Unlikeis_authorized/is_authorized_partial, there is no decision to fall back to when the input itself cannot be resolved.The
tpefeatureEnables
tpealongsidepartial-evalinCargo.toml. Checked the resolved lockfile before and after: no change.cedar-policy'stpe = ["cedar-policy-core/tpe"]andcedar-policy-core'stpe = []both have no dependency edges.cedar-policy4.12.0 putspartial-evalandtpein the same experimental bucket, and cedarpy already shipspartial-evalas its documented partial-authorization API.Testing
Built with
maturin develop --releaseon macOS arm64, Python 3.14.pytest tests/unitpytest tests/integration/test_cedar_integration_tests.pypytest tests/integration/test_cedar_corpus_tests.pygit diff Cargo.lockcargo clippy --releaseNew tests cover a type-only resource, a concrete resource that resolves allow and deny, a satisfied forbid overriding a satisfied permit while the permit stays in
true_ids, a residual forbid blocking an otherwise-true permit from becoming a decision, the residual as a typed node with a working pattern match on it, trivial residuals not repeated inresidual_policies,is_authorized_partialbehaving exactly as before, and error handling for a missing schema, an unparseable principal, a bare-type action, unparseable policies, and unparseable entities.Not run:
make benchmark-compare, same reason as #108.Marked draft because it stacks on #108, which stacks on #106.