diff --git a/Cargo.lock b/Cargo.lock index 754df4d..4ab7760 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2371,7 +2371,6 @@ dependencies = [ [[package]] name = "osiris" version = "0.1.0" -source = "git+https://git.ourworld.tf/herocode/osiris.git#097360ad12d2ea73ac4d38552889d97702d9a889" dependencies = [ "anyhow", "clap", @@ -2392,7 +2391,6 @@ dependencies = [ [[package]] name = "osiris_derive" version = "0.1.0" -source = "git+https://git.ourworld.tf/herocode/osiris.git#097360ad12d2ea73ac4d38552889d97702d9a889" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index d1f80f3..f4dd04b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,12 +12,20 @@ name = "runner_sal" path = "src/bin/runner_sal/main.rs" [[bin]] -name = "runner_osis" -path = "src/bin/runner_osis/main.rs" +name = "runner_osiris" +path = "src/bin/runner_osiris.rs" [[example]] -name = "engine" -path = "examples/engine.rs" +name = "sal" +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 @@ -48,7 +56,7 @@ heromodels_core = { 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" } 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-os = { git = "https://git.ourworld.tf/herocode/herolib_rust.git" } sal-redisclient = { git = "https://git.ourworld.tf/herocode/herolib_rust.git" } diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..e4daf51 --- /dev/null +++ b/examples/README.md @@ -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 diff --git a/examples/osiris/README.md b/examples/osiris/README.md new file mode 100644 index 0000000..6a3258c --- /dev/null +++ b/examples/osiris/README.md @@ -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 diff --git a/examples/osiris/access_denied.rhai b/examples/osiris/access_denied.rhai new file mode 100644 index 0000000..f276302 --- /dev/null +++ b/examples/osiris/access_denied.rhai @@ -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!" diff --git a/examples/osiris/event.rhai b/examples/osiris/event.rhai new file mode 100644 index 0000000..c609d74 --- /dev/null +++ b/examples/osiris/event.rhai @@ -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" diff --git a/examples/osiris/main.rs b/examples/osiris/main.rs new file mode 100644 index 0000000..186cc36 --- /dev/null +++ b/examples/osiris/main.rs @@ -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> { + 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(()) +} diff --git a/examples/osiris/note.rhai b/examples/osiris/note.rhai new file mode 100644 index 0000000..7bc74b1 --- /dev/null +++ b/examples/osiris/note.rhai @@ -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" diff --git a/examples/osiris/query.rhai b/examples/osiris/query.rhai new file mode 100644 index 0000000..97ff892 --- /dev/null +++ b/examples/osiris/query.rhai @@ -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" diff --git a/examples/osiris_example.rs b/examples/osiris_example.rs deleted file mode 100644 index 1688e5e..0000000 --- a/examples/osiris_example.rs +++ /dev/null @@ -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> { - 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(()) -} diff --git a/examples/osiris_script.rhai b/examples/osiris_script.rhai deleted file mode 100644 index 01f7bbe..0000000 --- a/examples/osiris_script.rhai +++ /dev/null @@ -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! ==="); diff --git a/examples/sal/README.md b/examples/sal/README.md new file mode 100644 index 0000000..039232e --- /dev/null +++ b/examples/sal/README.md @@ -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 diff --git a/examples/engine.rs b/examples/sal/main.rs similarity index 100% rename from examples/engine.rs rename to examples/sal/main.rs diff --git a/examples/scripts/_archive/03_process_management.rhai b/examples/sal/scripts/_archive/03_process_management.rhai similarity index 100% rename from examples/scripts/_archive/03_process_management.rhai rename to examples/sal/scripts/_archive/03_process_management.rhai diff --git a/examples/scripts/_archive/06_file_read_write.rhai b/examples/sal/scripts/_archive/06_file_read_write.rhai similarity index 100% rename from examples/scripts/_archive/06_file_read_write.rhai rename to examples/sal/scripts/_archive/06_file_read_write.rhai diff --git a/examples/scripts/_archive/container_example.rs b/examples/sal/scripts/_archive/container_example.rs similarity index 100% rename from examples/scripts/_archive/container_example.rs rename to examples/sal/scripts/_archive/container_example.rs diff --git a/examples/scripts/_archive/containerd_grpc_setup.rhai b/examples/sal/scripts/_archive/containerd_grpc_setup.rhai similarity index 100% rename from examples/scripts/_archive/containerd_grpc_setup.rhai rename to examples/sal/scripts/_archive/containerd_grpc_setup.rhai diff --git a/examples/scripts/_archive/download_test.rhai b/examples/sal/scripts/_archive/download_test.rhai similarity index 100% rename from examples/scripts/_archive/download_test.rhai rename to examples/sal/scripts/_archive/download_test.rhai diff --git a/examples/scripts/_archive/fs_test.rhai b/examples/sal/scripts/_archive/fs_test.rhai similarity index 100% rename from examples/scripts/_archive/fs_test.rhai rename to examples/sal/scripts/_archive/fs_test.rhai diff --git a/examples/scripts/_archive/install_deb.rhai b/examples/sal/scripts/_archive/install_deb.rhai similarity index 100% rename from examples/scripts/_archive/install_deb.rhai rename to examples/sal/scripts/_archive/install_deb.rhai diff --git a/examples/scripts/_archive/instructions_grpc.md b/examples/sal/scripts/_archive/instructions_grpc.md similarity index 100% rename from examples/scripts/_archive/instructions_grpc.md rename to examples/sal/scripts/_archive/instructions_grpc.md diff --git a/examples/scripts/_archive/package_management.rhai b/examples/sal/scripts/_archive/package_management.rhai similarity index 100% rename from examples/scripts/_archive/package_management.rhai rename to examples/sal/scripts/_archive/package_management.rhai diff --git a/examples/scripts/_archive/package_test.rs b/examples/sal/scripts/_archive/package_test.rs similarity index 100% rename from examples/scripts/_archive/package_test.rs rename to examples/sal/scripts/_archive/package_test.rs diff --git a/examples/scripts/_archive/process_long.rhai b/examples/sal/scripts/_archive/process_long.rhai similarity index 100% rename from examples/scripts/_archive/process_long.rhai rename to examples/sal/scripts/_archive/process_long.rhai diff --git a/examples/scripts/_archive/process_silent_test.rhai b/examples/sal/scripts/_archive/process_silent_test.rhai similarity index 100% rename from examples/scripts/_archive/process_silent_test.rhai rename to examples/sal/scripts/_archive/process_silent_test.rhai diff --git a/examples/scripts/_archive/process_test.rhai b/examples/sal/scripts/_archive/process_test.rhai similarity index 100% rename from examples/scripts/_archive/process_test.rhai rename to examples/sal/scripts/_archive/process_test.rhai diff --git a/examples/scripts/_archive/rfs_example.rhai b/examples/sal/scripts/_archive/rfs_example.rhai similarity index 100% rename from examples/scripts/_archive/rfs_example.rhai rename to examples/sal/scripts/_archive/rfs_example.rhai diff --git a/examples/scripts/_archive/run_all_tests.rhai b/examples/sal/scripts/_archive/run_all_tests.rhai similarity index 100% rename from examples/scripts/_archive/run_all_tests.rhai rename to examples/sal/scripts/_archive/run_all_tests.rhai diff --git a/examples/scripts/_archive/run_test.rhai b/examples/sal/scripts/_archive/run_test.rhai similarity index 100% rename from examples/scripts/_archive/run_test.rhai rename to examples/sal/scripts/_archive/run_test.rhai diff --git a/examples/scripts/_archive/sample.rhai b/examples/sal/scripts/_archive/sample.rhai similarity index 100% rename from examples/scripts/_archive/sample.rhai rename to examples/sal/scripts/_archive/sample.rhai diff --git a/examples/scripts/_archive/stdout_test.rhai b/examples/sal/scripts/_archive/stdout_test.rhai similarity index 100% rename from examples/scripts/_archive/stdout_test.rhai rename to examples/sal/scripts/_archive/stdout_test.rhai diff --git a/examples/scripts/_archive/text_tools.rhai b/examples/sal/scripts/_archive/text_tools.rhai similarity index 100% rename from examples/scripts/_archive/text_tools.rhai rename to examples/sal/scripts/_archive/text_tools.rhai diff --git a/examples/scripts/_archive/write_read.rhai b/examples/sal/scripts/_archive/write_read.rhai similarity index 100% rename from examples/scripts/_archive/write_read.rhai rename to examples/sal/scripts/_archive/write_read.rhai diff --git a/examples/scripts/basics/directories.rhai b/examples/sal/scripts/basics/directories.rhai similarity index 100% rename from examples/scripts/basics/directories.rhai rename to examples/sal/scripts/basics/directories.rhai diff --git a/examples/scripts/basics/files.rhai b/examples/sal/scripts/basics/files.rhai similarity index 100% rename from examples/scripts/basics/files.rhai rename to examples/sal/scripts/basics/files.rhai diff --git a/examples/scripts/basics/hello.rhai b/examples/sal/scripts/basics/hello.rhai similarity index 100% rename from examples/scripts/basics/hello.rhai rename to examples/sal/scripts/basics/hello.rhai diff --git a/examples/scripts/containers/buildah.rhai b/examples/sal/scripts/containers/buildah.rhai similarity index 100% rename from examples/scripts/containers/buildah.rhai rename to examples/sal/scripts/containers/buildah.rhai diff --git a/examples/scripts/containers/buildah_debug.rhai b/examples/sal/scripts/containers/buildah_debug.rhai similarity index 100% rename from examples/scripts/containers/buildah_debug.rhai rename to examples/sal/scripts/containers/buildah_debug.rhai diff --git a/examples/scripts/containers/buildah_run.rhai b/examples/sal/scripts/containers/buildah_run.rhai similarity index 100% rename from examples/scripts/containers/buildah_run.rhai rename to examples/sal/scripts/containers/buildah_run.rhai diff --git a/examples/scripts/containers/nerdctl_webserver.rhai b/examples/sal/scripts/containers/nerdctl_webserver.rhai similarity index 100% rename from examples/scripts/containers/nerdctl_webserver.rhai rename to examples/sal/scripts/containers/nerdctl_webserver.rhai diff --git a/examples/scripts/git/git_basic.rhai b/examples/sal/scripts/git/git_basic.rhai similarity index 100% rename from examples/scripts/git/git_basic.rhai rename to examples/sal/scripts/git/git_basic.rhai diff --git a/examples/scripts/hero_vault/README.md b/examples/sal/scripts/hero_vault/README.md similarity index 100% rename from examples/scripts/hero_vault/README.md rename to examples/sal/scripts/hero_vault/README.md diff --git a/examples/scripts/hero_vault/_archive/advanced_example.rhai b/examples/sal/scripts/hero_vault/_archive/advanced_example.rhai similarity index 100% rename from examples/scripts/hero_vault/_archive/advanced_example.rhai rename to examples/sal/scripts/hero_vault/_archive/advanced_example.rhai diff --git a/examples/scripts/hero_vault/_archive/agung_contract_with_args.rhai b/examples/sal/scripts/hero_vault/_archive/agung_contract_with_args.rhai similarity index 100% rename from examples/scripts/hero_vault/_archive/agung_contract_with_args.rhai rename to examples/sal/scripts/hero_vault/_archive/agung_contract_with_args.rhai diff --git a/examples/scripts/hero_vault/_archive/agung_send_transaction.rhai b/examples/sal/scripts/hero_vault/_archive/agung_send_transaction.rhai similarity index 100% rename from examples/scripts/hero_vault/_archive/agung_send_transaction.rhai rename to examples/sal/scripts/hero_vault/_archive/agung_send_transaction.rhai diff --git a/examples/scripts/hero_vault/_archive/contract_example.rhai b/examples/sal/scripts/hero_vault/_archive/contract_example.rhai similarity index 100% rename from examples/scripts/hero_vault/_archive/contract_example.rhai rename to examples/sal/scripts/hero_vault/_archive/contract_example.rhai diff --git a/examples/scripts/hero_vault/_archive/example.rhai b/examples/sal/scripts/hero_vault/_archive/example.rhai similarity index 100% rename from examples/scripts/hero_vault/_archive/example.rhai rename to examples/sal/scripts/hero_vault/_archive/example.rhai diff --git a/examples/scripts/hero_vault/_archive/key_persistence_example.rhai b/examples/sal/scripts/hero_vault/_archive/key_persistence_example.rhai similarity index 100% rename from examples/scripts/hero_vault/_archive/key_persistence_example.rhai rename to examples/sal/scripts/hero_vault/_archive/key_persistence_example.rhai diff --git a/examples/scripts/hero_vault/_archive/load_existing_space.rhai b/examples/sal/scripts/hero_vault/_archive/load_existing_space.rhai similarity index 100% rename from examples/scripts/hero_vault/_archive/load_existing_space.rhai rename to examples/sal/scripts/hero_vault/_archive/load_existing_space.rhai diff --git a/examples/scripts/kubernetes/basic_operations.rhai b/examples/sal/scripts/kubernetes/basic_operations.rhai similarity index 100% rename from examples/scripts/kubernetes/basic_operations.rhai rename to examples/sal/scripts/kubernetes/basic_operations.rhai diff --git a/examples/scripts/kubernetes/clusters/generic.rs b/examples/sal/scripts/kubernetes/clusters/generic.rs similarity index 100% rename from examples/scripts/kubernetes/clusters/generic.rs rename to examples/sal/scripts/kubernetes/clusters/generic.rs diff --git a/examples/scripts/kubernetes/clusters/postgres.rhai b/examples/sal/scripts/kubernetes/clusters/postgres.rhai similarity index 100% rename from examples/scripts/kubernetes/clusters/postgres.rhai rename to examples/sal/scripts/kubernetes/clusters/postgres.rhai diff --git a/examples/scripts/kubernetes/clusters/postgres.rs b/examples/sal/scripts/kubernetes/clusters/postgres.rs similarity index 100% rename from examples/scripts/kubernetes/clusters/postgres.rs rename to examples/sal/scripts/kubernetes/clusters/postgres.rs diff --git a/examples/scripts/kubernetes/clusters/redis.rhai b/examples/sal/scripts/kubernetes/clusters/redis.rhai similarity index 100% rename from examples/scripts/kubernetes/clusters/redis.rhai rename to examples/sal/scripts/kubernetes/clusters/redis.rhai diff --git a/examples/scripts/kubernetes/clusters/redis.rs b/examples/sal/scripts/kubernetes/clusters/redis.rs similarity index 100% rename from examples/scripts/kubernetes/clusters/redis.rs rename to examples/sal/scripts/kubernetes/clusters/redis.rs diff --git a/examples/scripts/kubernetes/multi_namespace_operations.rhai b/examples/sal/scripts/kubernetes/multi_namespace_operations.rhai similarity index 100% rename from examples/scripts/kubernetes/multi_namespace_operations.rhai rename to examples/sal/scripts/kubernetes/multi_namespace_operations.rhai diff --git a/examples/scripts/kubernetes/namespace_management.rhai b/examples/sal/scripts/kubernetes/namespace_management.rhai similarity index 100% rename from examples/scripts/kubernetes/namespace_management.rhai rename to examples/sal/scripts/kubernetes/namespace_management.rhai diff --git a/examples/scripts/kubernetes/pattern_deletion.rhai b/examples/sal/scripts/kubernetes/pattern_deletion.rhai similarity index 100% rename from examples/scripts/kubernetes/pattern_deletion.rhai rename to examples/sal/scripts/kubernetes/pattern_deletion.rhai diff --git a/examples/scripts/kubernetes/test_registration.rhai b/examples/sal/scripts/kubernetes/test_registration.rhai similarity index 100% rename from examples/scripts/kubernetes/test_registration.rhai rename to examples/sal/scripts/kubernetes/test_registration.rhai diff --git a/examples/scripts/mycelium/mycelium_basic.rhai b/examples/sal/scripts/mycelium/mycelium_basic.rhai similarity index 100% rename from examples/scripts/mycelium/mycelium_basic.rhai rename to examples/sal/scripts/mycelium/mycelium_basic.rhai diff --git a/examples/scripts/mycelium/mycelium_receive_message.rhai b/examples/sal/scripts/mycelium/mycelium_receive_message.rhai similarity index 100% rename from examples/scripts/mycelium/mycelium_receive_message.rhai rename to examples/sal/scripts/mycelium/mycelium_receive_message.rhai diff --git a/examples/scripts/mycelium/mycelium_send_message.rhai b/examples/sal/scripts/mycelium/mycelium_send_message.rhai similarity index 100% rename from examples/scripts/mycelium/mycelium_send_message.rhai rename to examples/sal/scripts/mycelium/mycelium_send_message.rhai diff --git a/examples/scripts/network/network_connectivity.rhai b/examples/sal/scripts/network/network_connectivity.rhai similarity index 100% rename from examples/scripts/network/network_connectivity.rhai rename to examples/sal/scripts/network/network_connectivity.rhai diff --git a/examples/scripts/network/network_rhai.rhai b/examples/sal/scripts/network/network_rhai.rhai similarity index 100% rename from examples/scripts/network/network_rhai.rhai rename to examples/sal/scripts/network/network_rhai.rhai diff --git a/examples/scripts/postgresclient/auth_example.rhai b/examples/sal/scripts/postgresclient/auth_example.rhai similarity index 100% rename from examples/scripts/postgresclient/auth_example.rhai rename to examples/sal/scripts/postgresclient/auth_example.rhai diff --git a/examples/scripts/postgresclient/basic_operations.rhai b/examples/sal/scripts/postgresclient/basic_operations.rhai similarity index 100% rename from examples/scripts/postgresclient/basic_operations.rhai rename to examples/sal/scripts/postgresclient/basic_operations.rhai diff --git a/examples/scripts/process/kill.rhai b/examples/sal/scripts/process/kill.rhai similarity index 100% rename from examples/scripts/process/kill.rhai rename to examples/sal/scripts/process/kill.rhai diff --git a/examples/scripts/process/process_get.rhai b/examples/sal/scripts/process/process_get.rhai similarity index 100% rename from examples/scripts/process/process_get.rhai rename to examples/sal/scripts/process/process_get.rhai diff --git a/examples/scripts/process/process_list.rhai b/examples/sal/scripts/process/process_list.rhai similarity index 100% rename from examples/scripts/process/process_list.rhai rename to examples/sal/scripts/process/process_list.rhai diff --git a/examples/scripts/process/run_all_options.rhai b/examples/sal/scripts/process/run_all_options.rhai similarity index 100% rename from examples/scripts/process/run_all_options.rhai rename to examples/sal/scripts/process/run_all_options.rhai diff --git a/examples/scripts/process/run_basic.rhai b/examples/sal/scripts/process/run_basic.rhai similarity index 100% rename from examples/scripts/process/run_basic.rhai rename to examples/sal/scripts/process/run_basic.rhai diff --git a/examples/scripts/process/run_ignore_error.rhai b/examples/sal/scripts/process/run_ignore_error.rhai similarity index 100% rename from examples/scripts/process/run_ignore_error.rhai rename to examples/sal/scripts/process/run_ignore_error.rhai diff --git a/examples/scripts/process/run_log.rhai b/examples/sal/scripts/process/run_log.rhai similarity index 100% rename from examples/scripts/process/run_log.rhai rename to examples/sal/scripts/process/run_log.rhai diff --git a/examples/scripts/process/run_silent.rhai b/examples/sal/scripts/process/run_silent.rhai similarity index 100% rename from examples/scripts/process/run_silent.rhai rename to examples/sal/scripts/process/run_silent.rhai diff --git a/examples/scripts/process/which.rhai b/examples/sal/scripts/process/which.rhai similarity index 100% rename from examples/scripts/process/which.rhai rename to examples/sal/scripts/process/which.rhai diff --git a/examples/scripts/redisclient/auth_example.rhai b/examples/sal/scripts/redisclient/auth_example.rhai similarity index 100% rename from examples/scripts/redisclient/auth_example.rhai rename to examples/sal/scripts/redisclient/auth_example.rhai diff --git a/examples/scripts/service_manager/README.md b/examples/sal/scripts/service_manager/README.md similarity index 100% rename from examples/scripts/service_manager/README.md rename to examples/sal/scripts/service_manager/README.md diff --git a/examples/scripts/service_manager/basic_usage.rhai b/examples/sal/scripts/service_manager/basic_usage.rhai similarity index 100% rename from examples/scripts/service_manager/basic_usage.rhai rename to examples/sal/scripts/service_manager/basic_usage.rhai diff --git a/examples/scripts/service_manager/circle_worker_manager.rhai b/examples/sal/scripts/service_manager/circle_worker_manager.rhai similarity index 100% rename from examples/scripts/service_manager/circle_worker_manager.rhai rename to examples/sal/scripts/service_manager/circle_worker_manager.rhai diff --git a/examples/scripts/simple.rhai b/examples/sal/scripts/simple.rhai similarity index 100% rename from examples/scripts/simple.rhai rename to examples/sal/scripts/simple.rhai diff --git a/examples/scripts/zinit/zinit_basic.rhai b/examples/sal/scripts/zinit/zinit_basic.rhai similarity index 100% rename from examples/scripts/zinit/zinit_basic.rhai rename to examples/sal/scripts/zinit/zinit_basic.rhai diff --git a/examples/scripts/zinit/zinit_basic2.rhai b/examples/sal/scripts/zinit/zinit_basic2.rhai similarity index 100% rename from examples/scripts/zinit/zinit_basic2.rhai rename to examples/sal/scripts/zinit/zinit_basic2.rhai diff --git a/examples/test_osiris.rs b/examples/test_osiris.rs deleted file mode 100644 index 318aa5d..0000000 --- a/examples/test_osiris.rs +++ /dev/null @@ -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> { - 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(()) -} diff --git a/examples/utils/README.md b/examples/utils/README.md new file mode 100644 index 0000000..54599a8 --- /dev/null +++ b/examples/utils/README.md @@ -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. diff --git a/examples/sign_job.rs b/examples/utils/sign_job.rs similarity index 100% rename from examples/sign_job.rs rename to examples/utils/sign_job.rs diff --git a/src/bin/runner_osiris.rs b/src/bin/runner_osiris.rs new file mode 100644 index 0000000..567fbb2 --- /dev/null +++ b/src/bin/runner_osiris.rs @@ -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, +} + +/// 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> { + // 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(()) +} diff --git a/src/bin/runner_osis/engine.rs b/src/bin/runner_osis/engine.rs index c6e5be5..dd4ede0 100644 --- a/src/bin/runner_osis/engine.rs +++ b/src/bin/runner_osis/engine.rs @@ -1,123 +1,14 @@ use rhai::Engine; -use rhailib_dsl; -use std::sync::{Arc, OnceLock}; -/// Engine factory for creating and sharing Rhai engines with DSL modules. -pub struct EngineFactory { - engine: Arc, -} - -impl EngineFactory { - /// 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 { - Arc::clone(&self.engine) - } - - /// Get the global singleton engine factory. - pub fn global() -> &'static EngineFactory { - static FACTORY: OnceLock = 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"); - - // 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. +/// Create a new OSIRIS engine instance. +/// +/// This simply delegates to osiris::rhai::create_osiris_engine which: +/// - Registers all OSIRIS functions (Note, Event, etc.) +/// - Sets up HeroDB context management +/// - Configures the Rhai engine for OSIRIS scripts pub fn create_osis_engine() -> Engine { - let mut engine = Engine::new(); - register_dsl_modules(&mut engine); - hero_logger::rhai_integration::configure_rhai_logging(&mut engine, "osis_runner"); - engine -} - -/// Create a shared osis engine using the factory. -pub fn create_shared_osis_engine() -> Arc { - EngineFactory::global().get_engine() + // Use the osiris engine creation - it handles everything + osiris::rhai::create_osiris_engine("default_owner", "redis://localhost:6379", 1) + .expect("Failed to create OSIRIS engine") + .0 // Return just the engine, not the scope } diff --git a/src/client.rs b/src/client.rs index 228a3bf..f4200c1 100644 --- a/src/client.rs +++ b/src/client.rs @@ -346,4 +346,97 @@ impl Client { 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 { + 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 { + 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 = 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; + } + } + } + } } diff --git a/src/engine/mod.rs b/src/engine/mod.rs index ef6e7fd..e89ac11 100644 --- a/src/engine/mod.rs +++ b/src/engine/mod.rs @@ -2,16 +2,13 @@ /// /// This module provides two different engine configurations: /// - `system`: SAL modules for system operations (async worker) -/// - `osis`: DSL modules for business operations (sync worker) -/// - `osiris`: DSL modules for business operations (sync worker) +/// - `osis`: OSIRIS engine for business operations (sync worker) pub mod system; pub mod osis; -pub mod osiris; pub use osis::create_osis_engine; pub use system::create_system_engine; -pub use osiris::{create_osiris_engine, run_osiris_script}; // Re-export common Rhai types for convenience pub use rhai::{Array, Dynamic, Engine, EvalAltResult, Map}; diff --git a/src/job.rs b/src/job.rs index 9164eb8..6d28c10 100644 --- a/src/job.rs +++ b/src/job.rs @@ -208,6 +208,7 @@ pub struct JobBuilder { executor: String, timeout: u64, // timeout in seconds env_vars: HashMap, + signatures: Vec, } impl JobBuilder { @@ -220,6 +221,7 @@ impl JobBuilder { executor: "".to_string(), timeout: 300, // 5 minutes default env_vars: HashMap::new(), + signatures: Vec::new(), } } @@ -277,6 +279,27 @@ impl JobBuilder { 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) -> Self { + self.signatures = signatures; + self + } + + /// Clear all signatures + pub fn clear_signatures(mut self) -> Self { + self.signatures.clear(); + self + } + /// Build the job pub fn build(self) -> Result { if self.caller_id.is_empty() { @@ -305,6 +328,7 @@ impl JobBuilder { job.timeout = self.timeout; job.env_vars = self.env_vars; + job.signatures = self.signatures; Ok(job) } diff --git a/src/script_mode.rs b/src/script_mode.rs index 3eb50c8..ff390f1 100644 --- a/src/script_mode.rs +++ b/src/script_mode.rs @@ -30,7 +30,6 @@ where // Create the job using JobBuilder let job = JobBuilder::new() .caller_id("script_mode") - .context_id("single_job") .payload(script_content) .runner(runner_id) .executor("rhai") diff --git a/src/sync_runner.rs b/src/sync_runner.rs index 7e732cf..99a8aa2 100644 --- a/src/sync_runner.rs +++ b/src/sync_runner.rs @@ -46,6 +46,24 @@ impl SyncRunner { 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("CONTEXT_ID".into(), job.context_id.clone().into()); + + // Extract signatories from job signatures, or fall back to env_vars + let signatories: Vec = 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::>(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)); debug!("Sync Runner for Context ID '{}': Evaluating script with Rhai engine (job context set).", job.context_id);