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>,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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<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.
 | 
					/// This simply delegates to osiris::rhai::create_osiris_engine which:
 | 
				
			||||||
/// It registers all business domain modules, making their functions available to Rhai scripts.
 | 
					/// - Registers all OSIRIS functions (Note, Event, etc.)
 | 
				
			||||||
///
 | 
					/// - Sets up HeroDB context management
 | 
				
			||||||
/// # Arguments
 | 
					/// - Configures the Rhai engine for OSIRIS scripts
 | 
				
			||||||
///
 | 
					 | 
				
			||||||
/// * `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