Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Way to Accomplish Async Builtins ( Maybe with RegoVM )? #730

Answered by zicklag
zicklag asked this question in Q&A
Discussion options

Hey there! I've got a use-case where policies might need to resolve remote member lists and therefore would benefit from an async builtin.

After looking around I'm seeing that there is actually a RegoVM, which is actually really close to what I want, because I'd like to drive the state machine myself, and have the VM suspended while I go do my async stuff, and then I can come back and continue execution until the next await point.

It looks like the RegoVM stuff is maybe new or not ready for general consumption?

Am I going in the right direction with this? If I run into blockers in RegoVM would you be open to updates that allow this workflow? I'd appreciate any hints!

You must be logged in to vote

Hey I found something that looks like it works!

The __builtin_host_await lets me trigger the host await, and I can just suffix the user's policy with a friendly wrapper around it to make nicer to use in the policy.

use regorus::{
 PolicyModule, Value,
 languages::rego::compiler::Compiler,
 rvm::{
 RegoVM,
 vm::{ExecutionMode, ExecutionState, SuspendReason},
 },
};
static POLICY: &str = r#"
package test
import rego.v1

default allow := false

allow if {
 user_exists(input.user_id) == "found"
}
"#;
static EXTENSIONS: &str = r#"
# Wrapper function around the host await builtin
user_exists(username) := result if {
 result := __builtin_host_await(username, "user_exists")
}
"#;

Replies: 2 comments 2 replies

Comment options

Hey I found something that looks like it works!

The __builtin_host_await lets me trigger the host await, and I can just suffix the user's policy with a friendly wrapper around it to make nicer to use in the policy.

use regorus::{
 PolicyModule, Value,
 languages::rego::compiler::Compiler,
 rvm::{
 RegoVM,
 vm::{ExecutionMode, ExecutionState, SuspendReason},
 },
};
static POLICY: &str = r#"
package test
import rego.v1

default allow := false

allow if {
 user_exists(input.user_id) == "found"
}
"#;
static EXTENSIONS: &str = r#"
# Wrapper function around the host await builtin
user_exists(username) := result if {
 result := __builtin_host_await(username, "user_exists")
}
"#;
fn main() -> anyhow::Result<()> {
 let compiled_policy = regorus::compile_policy_with_entrypoint(
 regorus::Value::Object(Default::default()),
 &[PolicyModule {
 id: "".into(),
 content: {
 let mut s = String::from(POLICY);
 s.push_str(EXTENSIONS);
 s
 }
 .into(),
 }],
 "data.test.allow".into(),
 )?;
 let program = Compiler::compile_from_policy(&compiled_policy, &["data.test.allow"])?;
 let mut vm = RegoVM::new();
 vm.load_program(program);
 vm.set_input(Value::from_json_str(
 r#"{
 "user_id": "user_abc123"
 }"#,
 )?);
 vm.set_execution_mode(ExecutionMode::Suspendable);
 vm.execute()?;
 loop {
 match vm.execution_state() {
 ExecutionState::Suspended {
 reason,
 ..
 } => {
 match reason {
 SuspendReason::HostAwait {
 argument,
 identifier,
 ..
 } => {
 let identifier_str = identifier.as_string()?.to_string();
 match identifier_str.as_str() {
 "user_exists" => {
 // Extract the query from the argument
 let query = &**argument.as_string()?;
 // Check for user
 let response = if query == "user_abc123" {
 "found".to_string()
 } else {
 "not_found".to_string()
 };
 // Provide the response to the async call
 vm.resume(Some(Value::from(response.as_str())))?;
 }
 other => {
 anyhow::bail!("Unknown async builtin identifier: {other:?}");
 }
 }
 }
 _ => panic!(),
 }
 }
 ExecutionState::Completed { result } => {
 println!("\n[VM] Completed — result: {result:?}");
 // Convert to bool for display
 let allowed = result.as_bool().copied().unwrap_or(false);
 println!("[RESULT] allow = {allowed}");
 break;
 }
 state => panic!("Unexpected state: {state:?}"),
 }
 }
 Ok(())
}
You must be logged in to vote
0 replies
Answer selected by zicklag
Comment options

Hey @zicklag,

The host await builtin and the suspendable mode execution were designed specifically to support scenarios such as yours where the policy needs to fetch external data.

Glad that you found it and were able to figure out a solution.

If I run into blockers in RegoVM would you be open to updates that allow this workflow

Yes, RVM is fully supported. Lot of work is being put into RVM - multi policy language support, serialized compiled policies, optimized execution, policy proving etc.
Here are some playgrounds to try out:

Note that the interpreter is also fully supported. But given that it is an AST walking interpreter, it may not be possible to implement all concepts (e.g. host await) in the interpreter.

You must be logged in to vote
2 replies
Comment options

Awesome, that's great. I really like being able to use RVM like this.

And thanks a lot for publishing this crate! I think it's going to be incredibly useful.

I'm using it to create a customizable organization / community account management layer for ATProto ( the protocol behind Bluesky ).

It's really nice to be able to take a language like this and use it to provide customizable policies instead of having to hardcode the policy into my server.

Comment options

That's a really cool use case, @zicklag! Sounds like a great fit!

Do let us know if you run into any issues with RVM/Regorus.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Category
Q&A
Labels
None yet
2 participants

AltStyle によって変換されたページ (->オリジナル) /