feat(billing): implement tenant subscription entitlements system (milestones 0-6)
Some checks failed
ci / ui (push) Failing after 28s
ci / rust (push) Failing after 2m40s
images / build-and-push (push) Failing after 19s

This commit is contained in:
2026-03-30 18:41:23 +03:00
parent 5992044b7e
commit 2595e7f1c5
63 changed files with 8448 additions and 321 deletions

View File

@@ -1,6 +1,7 @@
use serde_json::Value as JsonValue;
use std::time::Duration;
#[allow(unreachable_code)]
pub async fn execute_decide_program(
state: &JsonValue,
command: &JsonValue,
@@ -28,6 +29,7 @@ pub async fn execute_decide_program(
}
}
#[allow(unreachable_code)]
pub async fn execute_apply_program(
state: &JsonValue,
event: &JsonValue,
@@ -60,11 +62,10 @@ async fn execute_decide_v8(
state: &JsonValue,
command: &JsonValue,
program: &str,
gas_limit: u64,
_gas_limit: u64,
timeout: Duration,
) -> Result<Vec<JsonValue>, crate::types::AggregateError> {
use std::sync::Arc;
use v8::{Array, Context, Function, HandleScope, Isolate, Object, Scope, Script};
use v8::{Context, ContextScope, Function, HandleScope, Isolate, Script};
let state_str = serde_json::to_string(state).map_err(|e| {
crate::types::AggregateError::DecideError(format!("State serialization: {}", e))
@@ -73,47 +74,45 @@ async fn execute_decide_v8(
crate::types::AggregateError::DecideError(format!("Command serialization: {}", e))
})?;
let program_owned = program.to_string();
let result = tokio::task::spawn_blocking(move || {
let isolate = &mut Isolate::new(v8::CreateParams::default());
let scope = &mut HandleScope::new(isolate);
let context = Context::new(scope);
let context = Context::new(scope, v8::ContextOptions::default());
let scope = &mut ContextScope::new(scope, context);
let source =
v8::String::new(scope, program).ok_or_else(|| "Failed to create program string")?;
v8::String::new(scope, &program_owned).ok_or("Failed to create program string")?;
let script =
Script::compile(scope, source, None).ok_or_else(|| "Failed to compile program")?;
let script = Script::compile(scope, source, None).ok_or("Failed to compile program")?;
script.run(scope).ok_or_else(|| "Failed to run program")?;
script.run(scope).ok_or("Failed to run program")?;
let global = context.global(scope);
let decide_name =
v8::String::new(scope, "decide").ok_or_else(|| "Failed to create decide string")?;
v8::String::new(scope, "decide").ok_or("Failed to create decide string")?;
let decide_fn = global
.get(scope, decide_name.into())
.and_then(|v| v8::Local::<Function>::try_from(v).ok())
.ok_or_else(|| "decide function not found")?;
.ok_or("decide function not found")?;
let state_json = v8::String::new(scope, &state_str)
.ok_or_else(|| "Failed to create state JSON string")?;
let state_obj =
v8::json::parse(scope, state_json).ok_or_else(|| "Failed to parse state JSON")?;
let state_json =
v8::String::new(scope, &state_str).ok_or("Failed to create state JSON string")?;
let state_obj = v8::json::parse(scope, state_json).ok_or("Failed to parse state JSON")?;
let command_json = v8::String::new(scope, &command_str)
.ok_or_else(|| "Failed to create command JSON string")?;
let command_json =
v8::String::new(scope, &command_str).ok_or("Failed to create command JSON string")?;
let command_obj =
v8::json::parse(scope, command_json).ok_or_else(|| "Failed to parse command JSON")?;
v8::json::parse(scope, command_json).ok_or("Failed to parse command JSON")?;
let args: [v8::Local<v8::Value>; 2] = [state_obj.into(), command_obj.into()];
let args: [v8::Local<v8::Value>; 2] = [state_obj, command_obj];
let result = decide_fn
.call(scope, global.into(), &args)
.ok_or_else(|| "decide function call failed")?;
.ok_or("decide function call failed")?;
let result_json =
v8::json::stringify(scope, result).ok_or_else(|| "Failed to stringify result")?;
let result_json = v8::json::stringify(scope, result).ok_or("Failed to stringify result")?;
let result_str = result_json.to_rust_string_lossy(scope);
let events: Vec<JsonValue> = serde_json::from_str(&result_str)
@@ -155,47 +154,43 @@ async fn execute_apply_v8(
let _ = gas_limit;
let program_owned = program.to_string();
let result = tokio::task::spawn_blocking(move || {
let isolate = &mut Isolate::new(v8::CreateParams::default());
let scope = &mut HandleScope::new(isolate);
let context = Context::new(scope);
let context = Context::new(scope, v8::ContextOptions::default());
let scope = &mut ContextScope::new(scope, context);
let source =
v8::String::new(scope, program).ok_or_else(|| "Failed to create program string")?;
v8::String::new(scope, &program_owned).ok_or("Failed to create program string")?;
let script =
Script::compile(scope, source, None).ok_or_else(|| "Failed to compile program")?;
let script = Script::compile(scope, source, None).ok_or("Failed to compile program")?;
script.run(scope).ok_or_else(|| "Failed to run program")?;
script.run(scope).ok_or("Failed to run program")?;
let global = context.global(scope);
let apply_name =
v8::String::new(scope, "apply").ok_or_else(|| "Failed to create apply string")?;
let apply_name = v8::String::new(scope, "apply").ok_or("Failed to create apply string")?;
let apply_fn = global
.get(scope, apply_name.into())
.and_then(|v| v8::Local::<Function>::try_from(v).ok())
.ok_or_else(|| "apply function not found")?;
.ok_or("apply function not found")?;
let state_json = v8::String::new(scope, &state_str)
.ok_or_else(|| "Failed to create state JSON string")?;
let state_obj =
v8::json::parse(scope, state_json).ok_or_else(|| "Failed to parse state JSON")?;
let state_json =
v8::String::new(scope, &state_str).ok_or("Failed to create state JSON string")?;
let state_obj = v8::json::parse(scope, state_json).ok_or("Failed to parse state JSON")?;
let event_json = v8::String::new(scope, &event_str)
.ok_or_else(|| "Failed to create event JSON string")?;
let event_obj =
v8::json::parse(scope, event_json).ok_or_else(|| "Failed to parse event JSON")?;
let event_json =
v8::String::new(scope, &event_str).ok_or("Failed to create event JSON string")?;
let event_obj = v8::json::parse(scope, event_json).ok_or("Failed to parse event JSON")?;
let args: [v8::Local<v8::Value>; 2] = [state_obj.into(), event_obj.into()];
let args: [v8::Local<v8::Value>; 2] = [state_obj, event_obj];
let result = apply_fn
.call(scope, global.into(), &args)
.ok_or_else(|| "apply function call failed")?;
.ok_or("apply function call failed")?;
let result_json =
v8::json::stringify(scope, result).ok_or_else(|| "Failed to stringify result")?;
let result_json = v8::json::stringify(scope, result).ok_or("Failed to stringify result")?;
let result_str = result_json.to_rust_string_lossy(scope);
let new_state: JsonValue = serde_json::from_str(&result_str)
@@ -250,6 +245,7 @@ async fn execute_apply_wasm(
mod tests {
use super::*;
use serde_json::json;
use std::time::Duration;
#[tokio::test]
async fn no_runtime_returns_error() {
@@ -257,7 +253,7 @@ mod tests {
{
let state = json!({});
let command = json!({});
let result =
let result: Result<Vec<JsonValue>, crate::types::AggregateError> =
execute_decide_program(&state, &command, "program", 1000, Duration::from_secs(1))
.await;
assert!(result.is_err());