Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,696 changes: 2,696 additions & 0 deletions introspect/Cargo.lock

Large diffs are not rendered by default.

23 changes: 23 additions & 0 deletions introspect/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[workspace]

[package]
name = "iii-introspect"
version = "0.1.0"
edition = "2021"
publish = false

[[bin]]
name = "iii-introspect"
path = "src/main.rs"

[dependencies]
iii-sdk = "0.11.0"
tokio = { version = "1", features = ["rt-multi-thread", "macros", "sync", "signal"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
serde_yaml = "0.9"
anyhow = "1"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["fmt", "env-filter"] }
clap = { version = "4", features = ["derive"] }
chrono = { version = "0.4", features = ["serde"] }
61 changes: 61 additions & 0 deletions introspect/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# iii-introspect

When you build complex workflows with fan-out, reactive state, emit/subscribe chains, and 20+ steps — it becomes very hard to reason about what's happening. iii-introspect solves this. It traces specific workflows through their trigger chains, explains what each function does in plain language, generates focused Mermaid diagrams, and runs health checks. Ask it "how does my onboarding workflow work?" and it walks the full execution path.

**Plug and play:** Build with `cargo build --release`, then run `./target/release/iii-introspect --url ws://your-engine:49134`. It registers 9 functions and starts caching topology every 5 minutes. Call `introspect::trace_workflow` with any function ID to trace its dependency chain, or `introspect::explain` to get a business-level explanation of what it does and how it's triggered.

## Functions

| Function ID | Description |
|---|---|
| `introspect::functions` | List all registered functions in the engine |
| `introspect::workers` | List all connected workers |
| `introspect::triggers` | List all registered triggers |
| `introspect::topology` | Full system topology with stats (cached with TTL) |
| `introspect::diagram` | Generate a Mermaid flowchart of the system topology |
| `introspect::health` | Health check for orphaned functions, empty workers, duplicate IDs |
| `introspect::topology_refresh` | Cron-triggered cache refresh (internal, not exposed via HTTP) |

## iii Primitives Used

- **State** -- cached topology snapshot at `introspect:cache:topology`
- **Cron** -- periodic topology cache refresh
- **HTTP** -- all public functions exposed as GET endpoints

## Prerequisites

- Rust 1.75+
- Running iii engine on `ws://127.0.0.1:49134`

## Build

```bash
cargo build --release
```

## Usage

```bash
./target/release/iii-introspect --url ws://127.0.0.1:49134 --config ./config.yaml
```

```
Options:
--config <PATH> Path to config.yaml [default: ./config.yaml]
--url <URL> WebSocket URL of the iii engine [default: ws://127.0.0.1:49134]
--manifest Output module manifest as JSON and exit
-h, --help Print help
```

## Configuration

```yaml
cron_topology_refresh: "0 */5 * * * *" # refresh cache every 5 minutes
cache_ttl_seconds: 300 # TTL for cached topology data
```

## Tests

```bash
cargo test
```
6 changes: 6 additions & 0 deletions introspect/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
fn main() {
println!(
"cargo:rustc-env=TARGET={}",
std::env::var("TARGET").unwrap()
);
}
2 changes: 2 additions & 0 deletions introspect/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
cron_topology_refresh: "0 */5 * * * *"
cache_ttl_seconds: 300
63 changes: 63 additions & 0 deletions introspect/src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use anyhow::Result;
use serde::Deserialize;

#[derive(Deserialize, Debug, Clone)]
pub struct IntrospectConfig {
#[serde(default = "default_cron")]
pub cron_topology_refresh: String,
#[serde(default = "default_cache_ttl")]
pub cache_ttl_seconds: u64,
}

fn default_cron() -> String {
"0 */5 * * * *".to_string()
}

fn default_cache_ttl() -> u64 {
30
}

impl Default for IntrospectConfig {
fn default() -> Self {
IntrospectConfig {
cron_topology_refresh: default_cron(),
cache_ttl_seconds: default_cache_ttl(),
}
}
}

pub fn load_config(path: &str) -> Result<IntrospectConfig> {
let contents = std::fs::read_to_string(path)?;
let config: IntrospectConfig = serde_yaml::from_str(&contents)?;
Ok(config)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_config_defaults() {
let config: IntrospectConfig = serde_yaml::from_str("{}").unwrap();
assert_eq!(config.cron_topology_refresh, "0 */5 * * * *");
assert_eq!(config.cache_ttl_seconds, 30);
}

#[test]
fn test_config_custom() {
let yaml = r#"
cron_topology_refresh: "0 */10 * * * *"
cache_ttl_seconds: 60
"#;
let config: IntrospectConfig = serde_yaml::from_str(yaml).unwrap();
assert_eq!(config.cron_topology_refresh, "0 */10 * * * *");
assert_eq!(config.cache_ttl_seconds, 60);
}

#[test]
fn test_config_default_impl() {
let config = IntrospectConfig::default();
assert_eq!(config.cron_topology_refresh, "0 */5 * * * *");
assert_eq!(config.cache_ttl_seconds, 30);
}
}
Loading
Loading