# Architecture
```mermaid
flowchart TD
cli[jan CLI / clap] --> use{first token}
use -->|YAML command| match[match_commands]
match --> help[format_help]
match --> run[run_matched]
cfg --> load[load_spec]
load --> yaml[spec_load + include closure]
yaml --> remote[HTTPS fetch + sha256 cache]
run --> deps[env / PATH / requires / inputs]
run --> pkgs[packages uv pnpm gradle]
run --> warm{language leaf + runtime pool?}
warm -->|yes| pool[runtime_daemon workers]
warm -->|no / fallback| spawn[Command::new]
pool --> sqlite[audit.db]
spawn --> sqlite
```
## Modules (`src/`)
| `runner.rs` | Clap globals, dispatch to builtins vs matched leaves |
| `config.rs` | `jan use` preference (`jan_dir`, `spec_root`, remote source) |
| `spec_load.rs` | YAML load, platform filter, include graft, path confinement |
| `yaml_closure.rs` | Reachable local files for `jan bundle` |
| `lib.rs` | `RootSpec` / `CommandNode` / `ExecSpec` types, help, match, spawn, audit schema |
| `deps.rs` | Chain env, `pass`, PATH prefixes, `requires`, argv[0] resolution |
| `inputs.rs` | Typed flags and `${{ inputs.* }}` |
| `packages.rs` | Ensure / inspect / prune uv, pnpm, gradle envs; language argv helpers |
| `runtime_daemon.rs` | Jan-internal warm interpreter pool (python/node/shell/kotlin workers) |
| `unifier_events.rs` | Subscriber for Unifier's `events.sock`; notice → wakeup ([IPC contract](ipc-contract.md)) |
| `remote.rs` | HTTPS fetch, TTL cache, bundle unpack |
| `cron.rs` | Five-field expression matching |
| `cmdtest.rs` | `jan test` harness |
| `inspect.rs` | list / search / show / validate / audit / cron CLI |
| `builtins.rs` | use / bundle / alias |
The binary entry is `src/bin/jan.rs` → `jan_cli::run_jan()`.
## Trust and spawn
- Preferred tree path is sticky and unsigned ([Security](../security.md)).
- `argv[0]` resolves on inherited PATH before spec directories apply.
- Restricted child env when `env` is non-empty (essential allowlist + declared vars).
- Remote objects are hash-verified; cache root is `0700` on Unix.
- Language leaves prefer the [runtime pool](../cli/runtime.md) when the supervisor is up; each job still runs in an isolated child. `JAN_RUNTIME=0` forces cold `Command::new` spawns.
## Data locations
| Preference | `$JAN_CONFIG_DIR` or `~/.config/jan-cli/config.json` |
| Audit DB | `$JAN_DB` or XDG data local `jan-cli/audit.db` |
| Remote objects / trees / packages | `$JAN_CACHE_DIR` or `~/.cache/jan/` |
| Cron + runtime sockets | `$XDG_RUNTIME_DIR/jan-cli/` (or config `run/`) |
## Known debt
- YAML stack is unmaintained `serde_yaml` 0.9 / `unsafe-libyaml` (see `SECURITY.md` H1).
- No tree content digest in `config.json` (H3).
- No YAML depth/size caps (M4).