feat: reorganize examples and add signature support to JobBuilder
- Reorganized examples into osiris/, sal/, and utils/ folders - Moved hardcoded scripts to separate .rhai files - Added signature() method to JobBuilder for job signing - Updated OSIRIS context to use block_in_place instead of runtime - Removed runtime field from OsirisContext - Added typed save() methods for Note and Event objects - Updated all examples to use new structure and APIs
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -2371,7 +2371,6 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "osiris"
|
name = "osiris"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://git.ourworld.tf/herocode/osiris.git#097360ad12d2ea73ac4d38552889d97702d9a889"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
@@ -2392,7 +2391,6 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "osiris_derive"
|
name = "osiris_derive"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://git.ourworld.tf/herocode/osiris.git#097360ad12d2ea73ac4d38552889d97702d9a889"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|||||||
18
Cargo.toml
18
Cargo.toml
@@ -12,12 +12,20 @@ name = "runner_sal"
|
|||||||
path = "src/bin/runner_sal/main.rs"
|
path = "src/bin/runner_sal/main.rs"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "runner_osis"
|
name = "runner_osiris"
|
||||||
path = "src/bin/runner_osis/main.rs"
|
path = "src/bin/runner_osiris.rs"
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "engine"
|
name = "sal"
|
||||||
path = "examples/engine.rs"
|
path = "examples/sal/main.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "osiris"
|
||||||
|
path = "examples/osiris/main.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "sign_job"
|
||||||
|
path = "examples/utils/sign_job.rs"
|
||||||
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
@@ -48,7 +56,7 @@ heromodels_core = { git = "https://git.ourworld.tf/herocode/db.git" }
|
|||||||
heromodels-derive = { git = "https://git.ourworld.tf/herocode/db.git" }
|
heromodels-derive = { git = "https://git.ourworld.tf/herocode/db.git" }
|
||||||
rhailib_dsl = { git = "https://git.ourworld.tf/herocode/rhailib.git" }
|
rhailib_dsl = { git = "https://git.ourworld.tf/herocode/rhailib.git" }
|
||||||
hero_logger = { git = "https://git.ourworld.tf/herocode/baobab.git", branch = "logger" }
|
hero_logger = { git = "https://git.ourworld.tf/herocode/baobab.git", branch = "logger" }
|
||||||
osiris = { git = "https://git.ourworld.tf/herocode/osiris.git", features = ["rhai-support"] }
|
osiris = { path = "../osiris" }
|
||||||
# SAL modules for system engine
|
# SAL modules for system engine
|
||||||
sal-os = { git = "https://git.ourworld.tf/herocode/herolib_rust.git" }
|
sal-os = { git = "https://git.ourworld.tf/herocode/herolib_rust.git" }
|
||||||
sal-redisclient = { git = "https://git.ourworld.tf/herocode/herolib_rust.git" }
|
sal-redisclient = { git = "https://git.ourworld.tf/herocode/herolib_rust.git" }
|
||||||
|
|||||||
54
examples/README.md
Normal file
54
examples/README.md
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
# Runner Examples
|
||||||
|
|
||||||
|
This directory contains organized examples demonstrating different aspects of the runner system.
|
||||||
|
|
||||||
|
## Directory Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
examples/
|
||||||
|
├── osiris/ # OSIRIS context and object management examples
|
||||||
|
├── sal/ # System Abstraction Layer (SAL) DSL examples
|
||||||
|
└── utils/ # Utility examples (signing, crypto, etc.)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running Examples
|
||||||
|
|
||||||
|
### OSIRIS Example
|
||||||
|
Complete example demonstrating OSIRIS context management, Note and Event creation, and access control:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo run --example osiris
|
||||||
|
```
|
||||||
|
|
||||||
|
See [osiris/README.md](osiris/README.md) for details.
|
||||||
|
|
||||||
|
### SAL Example
|
||||||
|
Examples demonstrating various SAL DSL modules for system operations:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo run --example sal
|
||||||
|
```
|
||||||
|
|
||||||
|
See [sal/README.md](sal/README.md) for details.
|
||||||
|
|
||||||
|
### Utility Examples
|
||||||
|
Utility examples for cryptographic operations and job signing:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo run --example sign_job --features crypto
|
||||||
|
```
|
||||||
|
|
||||||
|
See [utils/README.md](utils/README.md) for details.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Redis server running on `localhost:6379`
|
||||||
|
- For OSIRIS examples: HeroDB instance (or uses local SQLite)
|
||||||
|
- For crypto examples: Enable the `crypto` feature flag
|
||||||
|
|
||||||
|
## Example Structure
|
||||||
|
|
||||||
|
Each example folder contains:
|
||||||
|
- `main.rs` - The main Rust example code
|
||||||
|
- `*.rhai` - Rhai script files demonstrating various features
|
||||||
|
- `README.md` - Detailed documentation for that example category
|
||||||
175
examples/osiris/README.md
Normal file
175
examples/osiris/README.md
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
# OSIRIS Complete Example
|
||||||
|
|
||||||
|
A comprehensive end-to-end example demonstrating the complete OSIRIS workflow.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
1. **Redis** - Must be running on localhost:6379
|
||||||
|
```bash
|
||||||
|
redis-server
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Rust** - Version 1.88+
|
||||||
|
```bash
|
||||||
|
rustup update
|
||||||
|
```
|
||||||
|
|
||||||
|
## Complete Example
|
||||||
|
|
||||||
|
### osiris_complete - Full End-to-End Workflow
|
||||||
|
|
||||||
|
**ONE EXAMPLE TO RULE THEM ALL** - Complete demonstration of OSIRIS.
|
||||||
|
|
||||||
|
**Run:**
|
||||||
|
```bash
|
||||||
|
cd /Users/timurgordon/code/git.ourworld.tf/herocode/runner_rust
|
||||||
|
cargo run --example osiris_complete
|
||||||
|
```
|
||||||
|
|
||||||
|
**What it demonstrates:**
|
||||||
|
1. **Runner Management** - Starts and stops runner_osiris daemon
|
||||||
|
2. **Job Client** - Creates client with builder pattern
|
||||||
|
3. **Job Building** - Uses JobBuilder for clean job creation
|
||||||
|
4. **Synchronous Execution** - Uses `run_job()` to wait for results
|
||||||
|
5. **OSIRIS Objects** - Creates Note and Event objects
|
||||||
|
6. **Context Storage** - Stores objects in participant-based contexts
|
||||||
|
7. **Data Querying** - Retrieves and lists stored data
|
||||||
|
8. **Access Control** - Demonstrates signatory-based access
|
||||||
|
9. **Error Handling** - Proper error propagation and cleanup
|
||||||
|
|
||||||
|
**Expected Output:**
|
||||||
|
```
|
||||||
|
🚀 OSIRIS Demo Script
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
|
||||||
|
Step 1: Getting context for participants [alice, bob]
|
||||||
|
✓ Context ID: alice,bob
|
||||||
|
|
||||||
|
Step 2: Creating a Note
|
||||||
|
✓ Note created with title: Project Planning Meeting
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Concepts
|
||||||
|
|
||||||
|
### Context Management
|
||||||
|
|
||||||
|
**Simple Logic:**
|
||||||
|
- Context = list of public keys (participants)
|
||||||
|
- To get_context, at least one participant must be a signatory
|
||||||
|
- No state tracking - contexts created fresh each time
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```rhai
|
||||||
|
// Signatories: [alice, bob, charlie]
|
||||||
|
|
||||||
|
// ✅ Works - alice is a signatory
|
||||||
|
let ctx = get_context(["alice", "bob"]);
|
||||||
|
|
||||||
|
// ❌ Fails - dave is not a signatory
|
||||||
|
let ctx = get_context(["alice", "dave"]);
|
||||||
|
```
|
||||||
|
|
||||||
|
### OSIRIS Objects
|
||||||
|
|
||||||
|
**Note:**
|
||||||
|
```rhai
|
||||||
|
let note = note("collection_name")
|
||||||
|
.title("My Note")
|
||||||
|
.content("Note content")
|
||||||
|
.tag("key", "value")
|
||||||
|
.mime("text/plain");
|
||||||
|
```
|
||||||
|
|
||||||
|
**Event:**
|
||||||
|
```rhai
|
||||||
|
let event = event("collection_name")
|
||||||
|
.title("My Event")
|
||||||
|
.description("Event description")
|
||||||
|
.location("Location")
|
||||||
|
.tag("type", "meeting");
|
||||||
|
```
|
||||||
|
|
||||||
|
### Context Operations
|
||||||
|
|
||||||
|
**Save:**
|
||||||
|
```rhai
|
||||||
|
let id = ctx.save("collection", "object_id", object);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Get:**
|
||||||
|
```rhai
|
||||||
|
let obj = ctx.get("collection", "object_id");
|
||||||
|
```
|
||||||
|
|
||||||
|
**List:**
|
||||||
|
```rhai
|
||||||
|
let ids = ctx.list("collection");
|
||||||
|
```
|
||||||
|
|
||||||
|
**Delete:**
|
||||||
|
```rhai
|
||||||
|
let deleted = ctx.delete("collection", "object_id");
|
||||||
|
```
|
||||||
|
|
||||||
|
**Query:**
|
||||||
|
```rhai
|
||||||
|
let results = ctx.query("collection", "field", "value");
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────┐
|
||||||
|
│ Rhai Script │
|
||||||
|
│ (osiris_demo.rhai)│
|
||||||
|
└──────────┬──────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────┐
|
||||||
|
│ OSIRIS Engine │
|
||||||
|
│ - Note API │
|
||||||
|
│ - Event API │
|
||||||
|
│ - get_context() │
|
||||||
|
└──────────┬──────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────┐
|
||||||
|
│ OsirisContext │
|
||||||
|
│ - Participants │
|
||||||
|
│ - CRUD operations │
|
||||||
|
└──────────┬──────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────┐
|
||||||
|
│ HeroDB │
|
||||||
|
│ (Redis + local DB) │
|
||||||
|
└─────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### "Connection refused" on port 6379
|
||||||
|
- Make sure Redis is running: `redis-server`
|
||||||
|
- Check: `redis-cli ping` (should return "PONG")
|
||||||
|
|
||||||
|
### "Access denied: none of the participants are signatories"
|
||||||
|
- Check that at least one participant is in the SIGNATORIES list
|
||||||
|
- Signatories are set via engine tags (see examples)
|
||||||
|
|
||||||
|
### "Failed to create context"
|
||||||
|
- Verify Redis is accessible
|
||||||
|
- Check HeroDB URL is correct
|
||||||
|
- Ensure db_id is valid (typically 1)
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. **Modify the scripts** - Edit `osiris_demo.rhai` to try different operations
|
||||||
|
2. **Add more objects** - Create your own object types
|
||||||
|
3. **Multi-context** - Try creating multiple contexts with different participants
|
||||||
|
4. **Integration** - Use OSIRIS in your own applications
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Status:** ✅ Ready to Use
|
||||||
|
**Last Updated:** 2025-10-24
|
||||||
8
examples/osiris/access_denied.rhai
Normal file
8
examples/osiris/access_denied.rhai
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
print("Attempting to access context with non-signatories...");
|
||||||
|
print("Participants: [dave, eve]");
|
||||||
|
print("Signatories: [alice, bob, charlie]");
|
||||||
|
|
||||||
|
// This should fail because neither dave nor eve are signatories
|
||||||
|
let ctx = get_context(["dave", "eve"]);
|
||||||
|
|
||||||
|
"This should not succeed!"
|
||||||
18
examples/osiris/event.rhai
Normal file
18
examples/osiris/event.rhai
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
print("Creating context for [alice, bob]...");
|
||||||
|
let ctx = get_context(["alice", "bob"]);
|
||||||
|
print("✓ Context ID: " + ctx.context_id());
|
||||||
|
|
||||||
|
print("\nCreating event...");
|
||||||
|
let event = event("events")
|
||||||
|
.title("Team Retrospective")
|
||||||
|
.description("Review what went well and areas for improvement")
|
||||||
|
.location("Virtual - Zoom Room A")
|
||||||
|
.category("retrospective");
|
||||||
|
|
||||||
|
print("✓ Event created");
|
||||||
|
|
||||||
|
print("\nStoring event in context...");
|
||||||
|
ctx.save(event);
|
||||||
|
print("✓ Event stored");
|
||||||
|
|
||||||
|
"Event 'Team Retrospective' created and stored successfully"
|
||||||
248
examples/osiris/main.rs
Normal file
248
examples/osiris/main.rs
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
/// Complete End-to-End OSIRIS Example
|
||||||
|
///
|
||||||
|
/// This example demonstrates the complete OSIRIS workflow:
|
||||||
|
/// 1. Starting the runner_osiris daemon
|
||||||
|
/// 2. Creating jobs with JobBuilder
|
||||||
|
/// 3. Running jobs with run_job() (synchronous)
|
||||||
|
/// 4. Creating OSIRIS objects (Note, Event)
|
||||||
|
/// 5. Storing objects in contexts
|
||||||
|
/// 6. Querying and retrieving data
|
||||||
|
/// 7. Proper cleanup
|
||||||
|
///
|
||||||
|
/// Prerequisites:
|
||||||
|
/// - Redis running on localhost:6379
|
||||||
|
///
|
||||||
|
/// Run with:
|
||||||
|
/// ```bash
|
||||||
|
/// cargo run --example osiris_complete
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
use runner_rust::job::{JobBuilder, Client};
|
||||||
|
use std::time::Duration;
|
||||||
|
use tokio::process::Command;
|
||||||
|
use tokio::time::sleep;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
println!("🚀 OSIRIS Complete End-to-End Example");
|
||||||
|
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// STEP 1: Start the runner daemon
|
||||||
|
// ========================================================================
|
||||||
|
println!("Step 1: Starting runner_osiris daemon");
|
||||||
|
println!("─────────────────────────────────────────────────────────────\n");
|
||||||
|
|
||||||
|
let mut runner = Command::new("cargo")
|
||||||
|
.args(&[
|
||||||
|
"run",
|
||||||
|
"--bin",
|
||||||
|
"runner_osiris",
|
||||||
|
"--",
|
||||||
|
"demo_runner",
|
||||||
|
"--db-path",
|
||||||
|
"/tmp/osiris_complete.db",
|
||||||
|
"--redis-url",
|
||||||
|
"redis://localhost:6379",
|
||||||
|
])
|
||||||
|
.spawn()?;
|
||||||
|
|
||||||
|
println!("✓ Runner started (PID: {})", runner.id().unwrap_or(0));
|
||||||
|
println!(" Runner ID: demo_runner");
|
||||||
|
println!(" Queue: demo_runner");
|
||||||
|
println!(" DB: /tmp/osiris_complete.db");
|
||||||
|
|
||||||
|
// Give the runner time to initialize
|
||||||
|
sleep(Duration::from_secs(2)).await;
|
||||||
|
println!("\n");
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// STEP 2: Create job client
|
||||||
|
// ========================================================================
|
||||||
|
println!("Step 2: Creating job client");
|
||||||
|
println!("─────────────────────────────────────────────────────────────\n");
|
||||||
|
|
||||||
|
let client = Client::builder()
|
||||||
|
.redis_url("redis://localhost:6379")
|
||||||
|
.build()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
println!("✓ Job client created\n");
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// STEP 3: Create and store a Note
|
||||||
|
// ========================================================================
|
||||||
|
println!("Step 3: Creating and Storing a Note");
|
||||||
|
println!("─────────────────────────────────────────────────────────────\n");
|
||||||
|
|
||||||
|
let create_note_script = std::fs::read_to_string("examples/osiris/note.rhai")?;
|
||||||
|
|
||||||
|
let job1 = JobBuilder::new()
|
||||||
|
.caller_id("example_client")
|
||||||
|
.context_id("demo_context")
|
||||||
|
.payload(create_note_script)
|
||||||
|
.runner("demo_runner")
|
||||||
|
.executor("rhai")
|
||||||
|
.timeout(30)
|
||||||
|
.signature("alice", "")
|
||||||
|
.signature("bob", "")
|
||||||
|
.signature("charlie", "")
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
println!("Running job: Create Note");
|
||||||
|
println!("Job ID: {}", job1.id);
|
||||||
|
|
||||||
|
match client.run_job(&job1, "demo_runner", 60).await {
|
||||||
|
Ok(result) => {
|
||||||
|
println!("\n✅ {}\n", result);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!("\n❌ Job failed: {}\n", e);
|
||||||
|
let _ = runner.kill().await;
|
||||||
|
return Err(format!("Job failed: {}", e).into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// STEP 4: Create and store an Event
|
||||||
|
// ========================================================================
|
||||||
|
println!("Step 4: Creating and Storing an Event");
|
||||||
|
println!("─────────────────────────────────────────────────────────────\n");
|
||||||
|
|
||||||
|
let create_event_script = std::fs::read_to_string("examples/osiris/event.rhai")?;
|
||||||
|
|
||||||
|
let job2 = JobBuilder::new()
|
||||||
|
.caller_id("example_client")
|
||||||
|
.context_id("demo_context")
|
||||||
|
.payload(create_event_script)
|
||||||
|
.runner("demo_runner")
|
||||||
|
.executor("rhai")
|
||||||
|
.timeout(30)
|
||||||
|
.signature("alice", "")
|
||||||
|
.signature("bob", "")
|
||||||
|
.signature("charlie", "")
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
println!("Running job: Create Event");
|
||||||
|
println!("Job ID: {}", job2.id);
|
||||||
|
|
||||||
|
match client.run_job(&job2, "demo_runner", 60).await {
|
||||||
|
Ok(result) => {
|
||||||
|
println!("\n✅ {}\n", result);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!("\n❌ Job failed: {}\n", e);
|
||||||
|
let _ = runner.kill().await;
|
||||||
|
return Err(format!("Job failed: {}", e).into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// STEP 5: Query context data
|
||||||
|
// ========================================================================
|
||||||
|
println!("Step 5: Querying Context Data");
|
||||||
|
println!("─────────────────────────────────────────────────────────────\n");
|
||||||
|
|
||||||
|
let query_script = std::fs::read_to_string("examples/osiris/query.rhai")?;
|
||||||
|
|
||||||
|
let job3 = JobBuilder::new()
|
||||||
|
.caller_id("example_client")
|
||||||
|
.context_id("demo_context")
|
||||||
|
.payload(query_script)
|
||||||
|
.runner("demo_runner")
|
||||||
|
.executor("rhai")
|
||||||
|
.timeout(30)
|
||||||
|
.signature("alice", "")
|
||||||
|
.signature("bob", "")
|
||||||
|
.signature("charlie", "")
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
println!("Running job: Query Data");
|
||||||
|
println!("Job ID: {}", job3.id);
|
||||||
|
|
||||||
|
match client.run_job(&job3, "demo_runner", 60).await {
|
||||||
|
Ok(result) => {
|
||||||
|
println!("\n✅ {}\n", result);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!("\n❌ Job failed: {}\n", e);
|
||||||
|
let _ = runner.kill().await;
|
||||||
|
return Err(format!("Job failed: {}", e).into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// STEP 6: Demonstrate access control
|
||||||
|
// ========================================================================
|
||||||
|
println!("Step 6: Testing Access Control");
|
||||||
|
println!("─────────────────────────────────────────────────────────────\n");
|
||||||
|
|
||||||
|
let access_denied_script = std::fs::read_to_string("examples/osiris/access_denied.rhai")?;
|
||||||
|
|
||||||
|
let job4 = JobBuilder::new()
|
||||||
|
.caller_id("example_client")
|
||||||
|
.context_id("demo_context")
|
||||||
|
.payload(access_denied_script)
|
||||||
|
.runner("demo_runner")
|
||||||
|
.executor("rhai")
|
||||||
|
.timeout(30)
|
||||||
|
.signature("alice", "")
|
||||||
|
.signature("bob", "")
|
||||||
|
.signature("charlie", "")
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
println!("Running job: Access Control Test");
|
||||||
|
println!("Job ID: {}", job4.id);
|
||||||
|
println!("Expected: Access denied\n");
|
||||||
|
|
||||||
|
match client.run_job(&job4, "demo_runner", 60).await {
|
||||||
|
Ok(result) => {
|
||||||
|
println!("❌ Unexpected success: {}\n", result);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!("✅ Access denied as expected");
|
||||||
|
println!(" Error: {}\n", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// STEP 7: Cleanup
|
||||||
|
// ========================================================================
|
||||||
|
println!("Step 7: Cleanup");
|
||||||
|
println!("─────────────────────────────────────────────────────────────\n");
|
||||||
|
|
||||||
|
println!("Stopping runner...");
|
||||||
|
runner.kill().await?;
|
||||||
|
println!("✓ Runner stopped\n");
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Summary
|
||||||
|
// ========================================================================
|
||||||
|
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
|
||||||
|
println!("🎉 Complete Example Finished!\n");
|
||||||
|
println!("📝 What We Demonstrated:");
|
||||||
|
println!(" ✓ Started runner_osiris daemon");
|
||||||
|
println!(" ✓ Created job client with builder pattern");
|
||||||
|
println!(" ✓ Built jobs with JobBuilder");
|
||||||
|
println!(" ✓ Used run_job() for synchronous execution");
|
||||||
|
println!(" ✓ Created OSIRIS Note object");
|
||||||
|
println!(" ✓ Created OSIRIS Event object");
|
||||||
|
println!(" ✓ Stored objects in contexts");
|
||||||
|
println!(" ✓ Queried and retrieved data");
|
||||||
|
println!(" ✓ Demonstrated participant-based access control");
|
||||||
|
println!(" ✓ Proper cleanup and shutdown");
|
||||||
|
println!("\n🏗️ Architecture:");
|
||||||
|
println!(" • Contexts defined by participant public keys");
|
||||||
|
println!(" • At least one participant must be a signatory");
|
||||||
|
println!(" • No state tracking - contexts created on demand");
|
||||||
|
println!(" • Jobs executed via Redis queue");
|
||||||
|
println!(" • Results returned synchronously");
|
||||||
|
println!("\n💡 Key Features:");
|
||||||
|
println!(" • Simple API: client.run_job()");
|
||||||
|
println!(" • Fluent builders: JobBuilder, note(), event()");
|
||||||
|
println!(" • Automatic waiting and result extraction");
|
||||||
|
println!(" • Proper error handling and timeouts");
|
||||||
|
println!(" • Production-ready job infrastructure");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
20
examples/osiris/note.rhai
Normal file
20
examples/osiris/note.rhai
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
print("Creating context for [alice, bob]...");
|
||||||
|
let ctx = get_context(["alice", "bob"]);
|
||||||
|
print("✓ Context ID: " + ctx.context_id());
|
||||||
|
|
||||||
|
print("\nCreating note...");
|
||||||
|
let note = note("notes")
|
||||||
|
.title("Sprint Planning Meeting")
|
||||||
|
.content("Discussed Q1 2025 roadmap and milestones")
|
||||||
|
.tag("sprint", "2025-Q1")
|
||||||
|
.tag("team", "engineering")
|
||||||
|
.tag("priority", "high")
|
||||||
|
.mime("text/markdown");
|
||||||
|
|
||||||
|
print("✓ Note created");
|
||||||
|
|
||||||
|
print("\nStoring note in context...");
|
||||||
|
ctx.save(note);
|
||||||
|
print("✓ Note stored");
|
||||||
|
|
||||||
|
"Note 'Sprint Planning Meeting' created and stored successfully"
|
||||||
21
examples/osiris/query.rhai
Normal file
21
examples/osiris/query.rhai
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
print("Querying context [alice, bob]...");
|
||||||
|
let ctx = get_context(["alice", "bob"]);
|
||||||
|
print("✓ Context ID: " + ctx.context_id());
|
||||||
|
|
||||||
|
print("\nListing all notes...");
|
||||||
|
let notes = ctx.list("notes");
|
||||||
|
print("✓ Found " + notes.len() + " note(s)");
|
||||||
|
|
||||||
|
print("\nRetrieving specific note...");
|
||||||
|
let note = ctx.get("notes", "sprint_planning_001");
|
||||||
|
print("✓ Retrieved note: sprint_planning_001");
|
||||||
|
|
||||||
|
print("\nQuerying context [alice, bob, charlie]...");
|
||||||
|
let ctx2 = get_context(["alice", "bob", "charlie"]);
|
||||||
|
print("✓ Context ID: " + ctx2.context_id());
|
||||||
|
|
||||||
|
print("\nListing all events...");
|
||||||
|
let events = ctx2.list("events");
|
||||||
|
print("✓ Found " + events.len() + " event(s)");
|
||||||
|
|
||||||
|
"Query complete: Found " + notes.len() + " notes and " + events.len() + " events"
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
/// Example: Running OSIRIS Rhai scripts
|
|
||||||
///
|
|
||||||
/// This example demonstrates how to use the OSIRIS Rhai engine
|
|
||||||
/// to execute scripts that create and manipulate OSIRIS objects.
|
|
||||||
///
|
|
||||||
/// Prerequisites:
|
|
||||||
/// - HeroDB running on localhost:6379
|
|
||||||
/// - osiris crate added as dependency with rhai-support feature
|
|
||||||
///
|
|
||||||
/// Run with:
|
|
||||||
/// ```bash
|
|
||||||
/// cargo run --example osiris_example
|
|
||||||
/// ```
|
|
||||||
|
|
||||||
use runner_rust::engine::osiris::run_osiris_script;
|
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
println!("🚀 OSIRIS Rhai Engine Example\n");
|
|
||||||
|
|
||||||
// Example 1: Inline script
|
|
||||||
println!("Example 1: Inline Script");
|
|
||||||
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
|
|
||||||
|
|
||||||
let script = r#"
|
|
||||||
print("Creating a note from inline script...");
|
|
||||||
let note = note("notes");
|
|
||||||
print(note);
|
|
||||||
"#;
|
|
||||||
|
|
||||||
run_osiris_script(script, "redis://localhost:6379", 1)?;
|
|
||||||
|
|
||||||
println!("\n✅ Example completed!");
|
|
||||||
println!("\nNote: Full OSIRIS integration requires adding osiris crate");
|
|
||||||
println!(" as a dependency with the 'rhai-support' feature enabled.");
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
// OSIRIS Rhai Script Example
|
|
||||||
// This script demonstrates creating and storing OSIRIS objects
|
|
||||||
|
|
||||||
print("=== OSIRIS Rhai Script Example ===\n");
|
|
||||||
|
|
||||||
// Create a note with fluent builder pattern
|
|
||||||
print("Creating a note...");
|
|
||||||
let note = note("notes")
|
|
||||||
.title("My First Rhai Note")
|
|
||||||
.content("This note was created from a Rhai script using the OSIRIS engine!")
|
|
||||||
.tag("source", "rhai")
|
|
||||||
.tag("project", "osiris")
|
|
||||||
.tag("priority", "high")
|
|
||||||
.mime("text/plain");
|
|
||||||
|
|
||||||
print(`Note created: ${note.get_title()}`);
|
|
||||||
|
|
||||||
// Store the note in HeroDB
|
|
||||||
print("Storing note...");
|
|
||||||
let note_id = put_note(note);
|
|
||||||
print(`✓ Note stored with ID: ${note_id}\n`);
|
|
||||||
|
|
||||||
// Retrieve the note
|
|
||||||
print("Retrieving note...");
|
|
||||||
let retrieved_note = get_note("notes", note_id);
|
|
||||||
print(`✓ Retrieved: ${retrieved_note.get_title()}`);
|
|
||||||
print(` Content: ${retrieved_note.get_content()}\n`);
|
|
||||||
|
|
||||||
// Create an event
|
|
||||||
print("Creating an event...");
|
|
||||||
let event = event("calendar", "Team Meeting")
|
|
||||||
.description("Weekly team sync")
|
|
||||||
.location("Conference Room A")
|
|
||||||
.category("meetings")
|
|
||||||
.all_day(false);
|
|
||||||
|
|
||||||
print(`Event created: ${event.get_title()}`);
|
|
||||||
|
|
||||||
// Store the event
|
|
||||||
print("Storing event...");
|
|
||||||
let event_id = put_event(event);
|
|
||||||
print(`✓ Event stored with ID: ${event_id}\n`);
|
|
||||||
|
|
||||||
// Query notes by tag
|
|
||||||
print("Querying notes by tag (project=osiris)...");
|
|
||||||
let ids = query("notes", "tags:tag", "project=osiris");
|
|
||||||
print(`✓ Found ${ids.len()} note(s) with tag project=osiris`);
|
|
||||||
|
|
||||||
for id in ids {
|
|
||||||
let n = get_note("notes", id);
|
|
||||||
print(` - ${n.get_title()}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
print("\n=== Script completed successfully! ===");
|
|
||||||
64
examples/sal/README.md
Normal file
64
examples/sal/README.md
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
# SAL (System Abstraction Layer) Examples
|
||||||
|
|
||||||
|
This directory contains examples demonstrating the SAL DSL modules for system operations.
|
||||||
|
|
||||||
|
## Running the Example
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo run --example sal
|
||||||
|
```
|
||||||
|
|
||||||
|
## Available Scripts
|
||||||
|
|
||||||
|
The `scripts/` directory contains various Rhai scripts demonstrating different SAL modules:
|
||||||
|
|
||||||
|
### Basics
|
||||||
|
- `basics/` - Basic Rhai language features and syntax
|
||||||
|
|
||||||
|
### Containers
|
||||||
|
- `containers/` - Container management operations
|
||||||
|
|
||||||
|
### Git
|
||||||
|
- `git/` - Git repository operations
|
||||||
|
|
||||||
|
### Hero Vault
|
||||||
|
- `hero_vault/` - Secure credential storage and retrieval
|
||||||
|
|
||||||
|
### Kubernetes
|
||||||
|
- `kubernetes/` - Kubernetes cluster management
|
||||||
|
|
||||||
|
### Mycelium
|
||||||
|
- `mycelium/` - Mycelium network operations
|
||||||
|
|
||||||
|
### Network
|
||||||
|
- `network/` - Network utilities and operations
|
||||||
|
|
||||||
|
### PostgreSQL
|
||||||
|
- `postgresclient/` - PostgreSQL database operations
|
||||||
|
|
||||||
|
### Process
|
||||||
|
- `process/` - Process management and execution
|
||||||
|
|
||||||
|
### Redis
|
||||||
|
- `redisclient/` - Redis operations
|
||||||
|
|
||||||
|
### Service Manager
|
||||||
|
- `service_manager/` - System service management
|
||||||
|
|
||||||
|
### Zinit
|
||||||
|
- `zinit/` - Zinit service manager operations
|
||||||
|
|
||||||
|
## Script Structure
|
||||||
|
|
||||||
|
Each script demonstrates:
|
||||||
|
- Module initialization
|
||||||
|
- Common operations
|
||||||
|
- Error handling
|
||||||
|
- Best practices
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
The example uses:
|
||||||
|
- Default database path: `/tmp/sal_engine.db`
|
||||||
|
- Context isolation per script
|
||||||
|
- Verbose output for debugging
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
/// Quick test of OSIRIS Rhai integration
|
|
||||||
///
|
|
||||||
/// Run with:
|
|
||||||
/// ```bash
|
|
||||||
/// cargo run --example test_osiris
|
|
||||||
/// ```
|
|
||||||
|
|
||||||
use runner_rust::engine::osiris::create_osiris_engine;
|
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
println!("🧪 Testing OSIRIS Rhai Engine\n");
|
|
||||||
|
|
||||||
// Test 1: Create engine
|
|
||||||
println!("Test 1: Creating OSIRIS engine...");
|
|
||||||
let engine = create_osiris_engine("redis://localhost:6379", 1)?;
|
|
||||||
println!("✓ Engine created successfully\n");
|
|
||||||
|
|
||||||
// Test 2: Run simple script
|
|
||||||
println!("Test 2: Running simple script...");
|
|
||||||
let script = r#"
|
|
||||||
print("Hello from OSIRIS Rhai!");
|
|
||||||
|
|
||||||
// Create a note
|
|
||||||
let note = note("test_notes")
|
|
||||||
.title("Test Note")
|
|
||||||
.content("This is a test")
|
|
||||||
.tag("test", "true");
|
|
||||||
|
|
||||||
print("Note created: " + note.get_title());
|
|
||||||
"#;
|
|
||||||
|
|
||||||
match engine.eval::<()>(script) {
|
|
||||||
Ok(_) => println!("✓ Script executed successfully\n"),
|
|
||||||
Err(e) => {
|
|
||||||
println!("✗ Script error: {}\n", e);
|
|
||||||
println!("Note: This is expected if HeroDB is not running");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("✅ Tests completed!");
|
|
||||||
println!("\nTo run full integration:");
|
|
||||||
println!("1. Start HeroDB: cd ../herodb && cargo run --release");
|
|
||||||
println!("2. Run: cargo run --example test_osiris");
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
20
examples/utils/README.md
Normal file
20
examples/utils/README.md
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# Utility Examples
|
||||||
|
|
||||||
|
This directory contains utility examples for working with the runner system.
|
||||||
|
|
||||||
|
## sign_job
|
||||||
|
|
||||||
|
Demonstrates how to:
|
||||||
|
- Create a job with signatories
|
||||||
|
- Sign the job with secp256k1 private keys
|
||||||
|
- Verify the signatures
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo run --example sign_job --features crypto
|
||||||
|
```
|
||||||
|
|
||||||
|
### Requirements
|
||||||
|
|
||||||
|
The `crypto` feature must be enabled to use cryptographic signing functionality.
|
||||||
117
src/bin/runner_osiris.rs
Normal file
117
src/bin/runner_osiris.rs
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
use runner_rust::{spawn_sync_runner, script_mode::execute_script_mode};
|
||||||
|
use clap::Parser;
|
||||||
|
use log::{error, info};
|
||||||
|
use tokio::sync::mpsc;
|
||||||
|
use rhai::Engine;
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[command(author, version, about, long_about = None)]
|
||||||
|
struct Args {
|
||||||
|
/// Runner ID
|
||||||
|
runner_id: String,
|
||||||
|
|
||||||
|
/// Database path
|
||||||
|
#[arg(short, long, default_value = "/tmp/osis.db")]
|
||||||
|
db_path: String,
|
||||||
|
|
||||||
|
/// Redis URL
|
||||||
|
#[arg(short = 'r', long, default_value = "redis://localhost:6379")]
|
||||||
|
redis_url: String,
|
||||||
|
|
||||||
|
/// Preserve tasks after completion
|
||||||
|
#[arg(short, long, default_value_t = false)]
|
||||||
|
preserve_tasks: bool,
|
||||||
|
|
||||||
|
/// Script to execute in single-job mode (optional)
|
||||||
|
#[arg(short, long)]
|
||||||
|
script: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new OSIRIS engine instance.
|
||||||
|
///
|
||||||
|
/// This creates an engine with dynamic context management via get_context():
|
||||||
|
/// - Registers all OSIRIS functions (Note, Event, etc.)
|
||||||
|
/// - Sets up get_context() for participant-based access control
|
||||||
|
/// - Configures the Rhai engine for OSIRIS scripts
|
||||||
|
fn create_osis_engine() -> Engine {
|
||||||
|
// Use the engine with manager for dynamic context creation
|
||||||
|
osiris::rhai::create_osiris_engine_with_manager("redis://localhost:6379", 1)
|
||||||
|
.expect("Failed to create OSIRIS engine")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
|
// Initialize logging
|
||||||
|
env_logger::init();
|
||||||
|
|
||||||
|
let args = Args::parse();
|
||||||
|
|
||||||
|
// Check if we're in script mode
|
||||||
|
if let Some(script_content) = args.script {
|
||||||
|
info!("Running in script mode with runner ID: {}", args.runner_id);
|
||||||
|
|
||||||
|
let result = execute_script_mode(
|
||||||
|
&script_content,
|
||||||
|
&args.runner_id,
|
||||||
|
args.redis_url,
|
||||||
|
std::time::Duration::from_secs(300), // Default timeout for OSIS
|
||||||
|
create_osis_engine,
|
||||||
|
).await;
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(output) => {
|
||||||
|
println!("Script execution result:\n{}", output);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Script execution failed: {}", e);
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("Starting OSIS Sync Runner with ID: {}", args.runner_id);
|
||||||
|
info!("Database path: {}", args.db_path);
|
||||||
|
info!("Redis URL: {}", args.redis_url);
|
||||||
|
info!("Preserve tasks: {}", args.preserve_tasks);
|
||||||
|
|
||||||
|
// Create shutdown channel
|
||||||
|
let (shutdown_tx, shutdown_rx) = mpsc::channel::<()>(1);
|
||||||
|
|
||||||
|
// Setup signal handling for graceful shutdown
|
||||||
|
let shutdown_tx_clone = shutdown_tx.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
tokio::signal::ctrl_c().await.expect("Failed to listen for ctrl+c");
|
||||||
|
info!("Received Ctrl+C, initiating shutdown...");
|
||||||
|
let _ = shutdown_tx_clone.send(()).await;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Spawn the sync runner with engine factory
|
||||||
|
let runner_handle = spawn_sync_runner(
|
||||||
|
args.runner_id.clone(),
|
||||||
|
args.db_path,
|
||||||
|
args.redis_url,
|
||||||
|
shutdown_rx,
|
||||||
|
args.preserve_tasks,
|
||||||
|
create_osis_engine,
|
||||||
|
);
|
||||||
|
|
||||||
|
info!("OSIS Sync Runner '{}' started successfully", args.runner_id);
|
||||||
|
|
||||||
|
// Wait for the runner to complete
|
||||||
|
match runner_handle.await {
|
||||||
|
Ok(Ok(())) => {
|
||||||
|
info!("OSIS Sync Runner '{}' shut down successfully", args.runner_id);
|
||||||
|
}
|
||||||
|
Ok(Err(e)) => {
|
||||||
|
error!("OSIS Sync Runner '{}' encountered an error: {}", args.runner_id, e);
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to join OSIS Sync Runner '{}' task: {}", args.runner_id, e);
|
||||||
|
return Err(Box::new(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -1,123 +1,14 @@
|
|||||||
use rhai::Engine;
|
use rhai::Engine;
|
||||||
use rhailib_dsl;
|
|
||||||
use std::sync::{Arc, OnceLock};
|
|
||||||
|
|
||||||
/// Engine factory for creating and sharing Rhai engines with DSL modules.
|
/// Create a new OSIRIS engine instance.
|
||||||
pub struct EngineFactory {
|
///
|
||||||
engine: Arc<Engine>,
|
/// This simply delegates to osiris::rhai::create_osiris_engine which:
|
||||||
}
|
/// - Registers all OSIRIS functions (Note, Event, etc.)
|
||||||
|
/// - Sets up HeroDB context management
|
||||||
impl EngineFactory {
|
/// - Configures the Rhai engine for OSIRIS scripts
|
||||||
/// Create a new engine factory with a configured Rhai engine.
|
|
||||||
pub fn new() -> Self {
|
|
||||||
let mut engine = Engine::new();
|
|
||||||
register_dsl_modules(&mut engine);
|
|
||||||
// Logger
|
|
||||||
hero_logger::rhai_integration::configure_rhai_logging(&mut engine, "osis_runner");
|
|
||||||
|
|
||||||
Self {
|
|
||||||
engine: Arc::new(engine),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a shared reference to the engine.
|
|
||||||
pub fn get_engine(&self) -> Arc<Engine> {
|
|
||||||
Arc::clone(&self.engine)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the global singleton engine factory.
|
|
||||||
pub fn global() -> &'static EngineFactory {
|
|
||||||
static FACTORY: OnceLock<EngineFactory> = OnceLock::new();
|
|
||||||
FACTORY.get_or_init(|| EngineFactory::new())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Register basic object functions directly in the engine.
|
|
||||||
/// This provides object functionality without relying on the problematic rhailib_dsl object module.
|
|
||||||
fn register_object_functions(engine: &mut Engine) {
|
|
||||||
use heromodels::models::object::Object;
|
|
||||||
|
|
||||||
// Register the Object type
|
|
||||||
engine.register_type_with_name::<Object>("Object");
|
|
||||||
|
|
||||||
// Register constructor function
|
|
||||||
engine.register_fn("new_object", || Object::new());
|
|
||||||
|
|
||||||
// Register setter functions
|
|
||||||
engine.register_fn("object_title", |obj: &mut Object, title: String| {
|
|
||||||
obj.title = title;
|
|
||||||
obj.clone()
|
|
||||||
});
|
|
||||||
|
|
||||||
engine.register_fn(
|
|
||||||
"object_description",
|
|
||||||
|obj: &mut Object, description: String| {
|
|
||||||
obj.description = description;
|
|
||||||
obj.clone()
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// Register getter functions
|
|
||||||
engine.register_fn("get_object_id", |obj: &mut Object| obj.id() as i64);
|
|
||||||
engine.register_fn("get_object_title", |obj: &mut Object| obj.title.clone());
|
|
||||||
engine.register_fn("get_object_description", |obj: &mut Object| {
|
|
||||||
obj.description.clone()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Registers all DSL modules with the provided Rhai engine.
|
|
||||||
///
|
|
||||||
/// This function is the main entry point for integrating the rhailib DSL with a Rhai engine.
|
|
||||||
/// It registers all business domain modules, making their functions available to Rhai scripts.
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// * `engine` - A mutable reference to the Rhai engine to register modules with
|
|
||||||
///
|
|
||||||
/// # Registered Modules
|
|
||||||
///
|
|
||||||
/// This function registers the following domain modules:
|
|
||||||
/// - Access control functions
|
|
||||||
/// - Business operation functions (companies, products, sales, shareholders)
|
|
||||||
/// - Calendar and scheduling functions
|
|
||||||
/// - Circle and community management functions
|
|
||||||
/// - Company management functions
|
|
||||||
/// - Contact management functions
|
|
||||||
/// - Core utility functions
|
|
||||||
/// - Financial operation functions (accounts, assets, marketplace)
|
|
||||||
/// - Workflow management functions (flows, steps, signatures)
|
|
||||||
/// - Library and content management functions
|
|
||||||
/// - Generic object manipulation functions (custom implementation)
|
|
||||||
pub fn register_dsl_modules(engine: &mut Engine) {
|
|
||||||
rhailib_dsl::access::register_access_rhai_module(engine);
|
|
||||||
rhailib_dsl::biz::register_biz_rhai_module(engine);
|
|
||||||
rhailib_dsl::calendar::register_calendar_rhai_module(engine);
|
|
||||||
rhailib_dsl::circle::register_circle_rhai_module(engine);
|
|
||||||
rhailib_dsl::company::register_company_rhai_module(engine);
|
|
||||||
rhailib_dsl::contact::register_contact_rhai_module(engine);
|
|
||||||
rhailib_dsl::core::register_core_rhai_module(engine);
|
|
||||||
rhailib_dsl::finance::register_finance_rhai_modules(engine);
|
|
||||||
// rhailib_dsl::flow::register_flow_rhai_modules(engine);
|
|
||||||
rhailib_dsl::library::register_library_rhai_module(engine);
|
|
||||||
// Skip problematic object module for now - can be implemented separately if needed
|
|
||||||
// rhailib_dsl::object::register_object_fns(engine);
|
|
||||||
rhailib_dsl::payment::register_payment_rhai_module(engine);
|
|
||||||
|
|
||||||
// Register basic object functionality directly
|
|
||||||
register_object_functions(engine);
|
|
||||||
|
|
||||||
println!("Rhailib Domain Specific Language modules registered successfully.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new osis engine instance.
|
|
||||||
pub fn create_osis_engine() -> Engine {
|
pub fn create_osis_engine() -> Engine {
|
||||||
let mut engine = Engine::new();
|
// Use the osiris engine creation - it handles everything
|
||||||
register_dsl_modules(&mut engine);
|
osiris::rhai::create_osiris_engine("default_owner", "redis://localhost:6379", 1)
|
||||||
hero_logger::rhai_integration::configure_rhai_logging(&mut engine, "osis_runner");
|
.expect("Failed to create OSIRIS engine")
|
||||||
engine
|
.0 // Return just the engine, not the scope
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a shared osis engine using the factory.
|
|
||||||
pub fn create_shared_osis_engine() -> Arc<Engine> {
|
|
||||||
EngineFactory::global().get_engine()
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -346,4 +346,97 @@ impl Client {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Run a job: dispatch it, wait for completion, and return the result
|
||||||
|
///
|
||||||
|
/// This is a convenience method that:
|
||||||
|
/// 1. Stores the job in Redis
|
||||||
|
/// 2. Dispatches it to the runner's queue
|
||||||
|
/// 3. Waits for the job to complete (polls status)
|
||||||
|
/// 4. Returns the result or error
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `job` - The job to run
|
||||||
|
/// * `runner_name` - The name of the runner to dispatch to
|
||||||
|
/// * `timeout_secs` - Maximum time to wait for job completion (in seconds)
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// * `Ok(String)` - The job result if successful
|
||||||
|
/// * `Err(JobError)` - If the job fails, times out, or encounters an error
|
||||||
|
pub async fn run_job(
|
||||||
|
&self,
|
||||||
|
job: &crate::job::Job,
|
||||||
|
runner_name: &str,
|
||||||
|
timeout_secs: u64,
|
||||||
|
) -> Result<String, JobError> {
|
||||||
|
use tokio::time::{Duration, timeout};
|
||||||
|
|
||||||
|
// Store the job in Redis
|
||||||
|
self.store_job_in_redis(job).await?;
|
||||||
|
|
||||||
|
// Dispatch to runner queue
|
||||||
|
self.dispatch_job(&job.id, runner_name).await?;
|
||||||
|
|
||||||
|
// Wait for job to complete with timeout
|
||||||
|
let result = timeout(
|
||||||
|
Duration::from_secs(timeout_secs),
|
||||||
|
self.wait_for_job_completion(&job.id)
|
||||||
|
).await;
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(Ok(job_result)) => Ok(job_result),
|
||||||
|
Ok(Err(e)) => Err(e),
|
||||||
|
Err(_) => Err(JobError::Timeout(format!(
|
||||||
|
"Job {} did not complete within {} seconds",
|
||||||
|
job.id, timeout_secs
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wait for a job to complete by polling its status
|
||||||
|
///
|
||||||
|
/// This polls the job status every 500ms until it reaches a terminal state
|
||||||
|
/// (Finished or Error), then returns the result or error.
|
||||||
|
async fn wait_for_job_completion(&self, job_id: &str) -> Result<String, JobError> {
|
||||||
|
use tokio::time::{sleep, Duration};
|
||||||
|
|
||||||
|
loop {
|
||||||
|
// Check job status
|
||||||
|
let status = self.get_status(job_id).await?;
|
||||||
|
|
||||||
|
match status {
|
||||||
|
JobStatus::Finished => {
|
||||||
|
// Job completed successfully, get the result
|
||||||
|
let result = self.get_result(job_id).await?;
|
||||||
|
return result.ok_or_else(|| {
|
||||||
|
JobError::InvalidData(format!("Job {} finished but has no result", job_id))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
JobStatus::Error => {
|
||||||
|
// Job failed, get the error message
|
||||||
|
let mut conn = self.redis_client
|
||||||
|
.get_multiplexed_async_connection()
|
||||||
|
.await
|
||||||
|
.map_err(|e| JobError::Redis(e))?;
|
||||||
|
|
||||||
|
let error_msg: Option<String> = conn
|
||||||
|
.hget(&self.job_key(job_id), "error")
|
||||||
|
.await
|
||||||
|
.map_err(|e| JobError::Redis(e))?;
|
||||||
|
|
||||||
|
return Err(JobError::InvalidData(
|
||||||
|
error_msg.unwrap_or_else(|| format!("Job {} failed with unknown error", job_id))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
JobStatus::Stopping => {
|
||||||
|
return Err(JobError::InvalidData(format!("Job {} was stopped", job_id)));
|
||||||
|
}
|
||||||
|
// Job is still running (Dispatched, WaitingForPrerequisites, Started)
|
||||||
|
_ => {
|
||||||
|
// Wait before polling again
|
||||||
|
sleep(Duration::from_millis(500)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,16 +2,13 @@
|
|||||||
///
|
///
|
||||||
/// This module provides two different engine configurations:
|
/// This module provides two different engine configurations:
|
||||||
/// - `system`: SAL modules for system operations (async worker)
|
/// - `system`: SAL modules for system operations (async worker)
|
||||||
/// - `osis`: DSL modules for business operations (sync worker)
|
/// - `osis`: OSIRIS engine for business operations (sync worker)
|
||||||
/// - `osiris`: DSL modules for business operations (sync worker)
|
|
||||||
|
|
||||||
pub mod system;
|
pub mod system;
|
||||||
pub mod osis;
|
pub mod osis;
|
||||||
pub mod osiris;
|
|
||||||
|
|
||||||
pub use osis::create_osis_engine;
|
pub use osis::create_osis_engine;
|
||||||
pub use system::create_system_engine;
|
pub use system::create_system_engine;
|
||||||
pub use osiris::{create_osiris_engine, run_osiris_script};
|
|
||||||
|
|
||||||
// Re-export common Rhai types for convenience
|
// Re-export common Rhai types for convenience
|
||||||
pub use rhai::{Array, Dynamic, Engine, EvalAltResult, Map};
|
pub use rhai::{Array, Dynamic, Engine, EvalAltResult, Map};
|
||||||
|
|||||||
24
src/job.rs
24
src/job.rs
@@ -208,6 +208,7 @@ pub struct JobBuilder {
|
|||||||
executor: String,
|
executor: String,
|
||||||
timeout: u64, // timeout in seconds
|
timeout: u64, // timeout in seconds
|
||||||
env_vars: HashMap<String, String>,
|
env_vars: HashMap<String, String>,
|
||||||
|
signatures: Vec<JobSignature>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JobBuilder {
|
impl JobBuilder {
|
||||||
@@ -220,6 +221,7 @@ impl JobBuilder {
|
|||||||
executor: "".to_string(),
|
executor: "".to_string(),
|
||||||
timeout: 300, // 5 minutes default
|
timeout: 300, // 5 minutes default
|
||||||
env_vars: HashMap::new(),
|
env_vars: HashMap::new(),
|
||||||
|
signatures: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,6 +279,27 @@ impl JobBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add a signature (public key and signature)
|
||||||
|
pub fn signature(mut self, public_key: &str, signature: &str) -> Self {
|
||||||
|
self.signatures.push(JobSignature {
|
||||||
|
public_key: public_key.to_string(),
|
||||||
|
signature: signature.to_string(),
|
||||||
|
});
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set multiple signatures
|
||||||
|
pub fn signatures(mut self, signatures: Vec<JobSignature>) -> Self {
|
||||||
|
self.signatures = signatures;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clear all signatures
|
||||||
|
pub fn clear_signatures(mut self) -> Self {
|
||||||
|
self.signatures.clear();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Build the job
|
/// Build the job
|
||||||
pub fn build(self) -> Result<Job, JobError> {
|
pub fn build(self) -> Result<Job, JobError> {
|
||||||
if self.caller_id.is_empty() {
|
if self.caller_id.is_empty() {
|
||||||
@@ -305,6 +328,7 @@ impl JobBuilder {
|
|||||||
|
|
||||||
job.timeout = self.timeout;
|
job.timeout = self.timeout;
|
||||||
job.env_vars = self.env_vars;
|
job.env_vars = self.env_vars;
|
||||||
|
job.signatures = self.signatures;
|
||||||
|
|
||||||
Ok(job)
|
Ok(job)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ where
|
|||||||
// Create the job using JobBuilder
|
// Create the job using JobBuilder
|
||||||
let job = JobBuilder::new()
|
let job = JobBuilder::new()
|
||||||
.caller_id("script_mode")
|
.caller_id("script_mode")
|
||||||
.context_id("single_job")
|
|
||||||
.payload(script_content)
|
.payload(script_content)
|
||||||
.runner(runner_id)
|
.runner(runner_id)
|
||||||
.executor("rhai")
|
.executor("rhai")
|
||||||
|
|||||||
@@ -46,6 +46,24 @@ impl SyncRunner {
|
|||||||
db_config.insert("DB_PATH".into(), db_path.to_string().into());
|
db_config.insert("DB_PATH".into(), db_path.to_string().into());
|
||||||
db_config.insert("CALLER_ID".into(), job.caller_id.clone().into());
|
db_config.insert("CALLER_ID".into(), job.caller_id.clone().into());
|
||||||
db_config.insert("CONTEXT_ID".into(), job.context_id.clone().into());
|
db_config.insert("CONTEXT_ID".into(), job.context_id.clone().into());
|
||||||
|
|
||||||
|
// Extract signatories from job signatures, or fall back to env_vars
|
||||||
|
let signatories: Vec<Dynamic> = if !job.signatures.is_empty() {
|
||||||
|
// Use signatures from the job
|
||||||
|
job.signatures.iter()
|
||||||
|
.map(|sig| Dynamic::from(sig.public_key.clone()))
|
||||||
|
.collect()
|
||||||
|
} else if let Some(sig_json) = job.env_vars.get("SIGNATORIES") {
|
||||||
|
// Fall back to SIGNATORIES from env_vars (for backward compatibility)
|
||||||
|
match serde_json::from_str::<Vec<String>>(sig_json) {
|
||||||
|
Ok(sigs) => sigs.into_iter().map(Dynamic::from).collect(),
|
||||||
|
Err(_) => Vec::new(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
};
|
||||||
|
db_config.insert("SIGNATORIES".into(), Dynamic::from(signatories));
|
||||||
|
|
||||||
engine.set_default_tag(Dynamic::from(db_config));
|
engine.set_default_tag(Dynamic::from(db_config));
|
||||||
|
|
||||||
debug!("Sync Runner for Context ID '{}': Evaluating script with Rhai engine (job context set).", job.context_id);
|
debug!("Sync Runner for Context ID '{}': Evaluating script with Rhai engine (job context set).", job.context_id);
|
||||||
|
|||||||
Reference in New Issue
Block a user