1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
//! Warble front-end compiler (`warble`): parses a Warble project (profile + components + context
//! binding), merges component defaults with profile overrides, runs the loud-fail compile checks,
//! and emits the language-neutral IR JSON that any back-end dispatcher consumes.
//!
//! # Architecture
//!
//! ```text
//! profile + components + context ──► warble compile ──► IR JSON ──► warble dispatch ──► native agent
//! (declarative YAML + prompts) (front-end, Rust) (the seam) (per-target back-end)
//! ```
//!
//! This crate is the first arrow: it owns the authoring types ([`ProfileFile`], [`ComponentFile`],
//! and friends), [`ContextLoader`] (the host's semantic-layer probe), and [`compile`], which
//! resolves a parsed project into IR. Everything downstream of the IR — the `claude-code-cli` and
//! `vercel` back-ends (both fold into the `warble` binary), plus the separate TS
//! `claude-agent-sdk` back-end — depends only on the IR schema, never on this crate's Rust types.
//! See [`authoring.md`][spec-authoring] for the authoring-side contract and
//! [`ir-schema.md`][spec-ir] for the emitted shape.
//!
//! # Sans-IO
//!
//! This crate performs **no filesystem or network access**. [`compile`] takes already-parsed
//! authoring values, the raw step markdown, and a [`ContextLoader`] the caller has already built
//! from the bound semantic layer — it never reads a path itself. The example below is exactly
//! this: it deserializes YAML strings in-memory and calls [`compile`] directly, no disk involved.
//! Keeping the core pure is what lets the same crate target native, WASM, and language bindings
//! unchanged; the native host adapter lives in the `warble-cli` crate.
//!
//! # Example
//!
//! A minimal project — one component, one step — compiled against an empty in-memory
//! [`ContextLoader`]. Both the profile and the component are parsed from YAML exactly as a host
//! would read them off disk; only the reading itself is out of scope for this sans-IO crate.
//!
//! ```
//! # use std::collections::HashMap;
//! # use warble::{ContextLoader, DimensionInfo, LineageGraph, MetricInfo, ModelInfo};
//! # struct EmptyContext(LineageGraph);
//! # impl ContextLoader for EmptyContext {
//! # fn is_parseable(&self) -> bool { true }
//! # fn metrics(&self) -> &[MetricInfo] { &[] }
//! # fn dimensions(&self) -> &[DimensionInfo] { &[] }
//! # fn time_dimensions(&self) -> &[DimensionInfo] { &[] }
//! # fn models(&self) -> &[ModelInfo] { &[] }
//! # fn lineage(&self) -> &LineageGraph { &self.0 }
//! # }
//! use warble::{compile, BindingFile, ComponentFile, ProfileFile, SlotContents};
//!
//! let profile: ProfileFile = serde_yaml::from_str(concat!(
//! "profile: demo\n",
//! "context:\n",
//! " project: ./context/binding.yml\n",
//! "components:\n",
//! " - use: hello\n",
//! ))?;
//! let binding: BindingFile = serde_yaml::from_str("kind: external\nproject: ./warehouse\n")?;
//! let component: ComponentFile = serde_yaml::from_str(concat!(
//! "id: hello\n",
//! "verb: greet\n",
//! "type: analytical\n",
//! "realization_kind: skill\n",
//! "binding_mode: runtime_selected\n",
//! "llm_steps:\n",
//! " - name: say_hello\n",
//! " tier: cheap\n",
//! " prompt_ref: steps/say_hello.md\n",
//! "trigger:\n",
//! " kind: one_shot\n",
//! "guardrails:\n",
//! " - name: read_only_execution\n",
//! " locked: true\n",
//! "effect:\n",
//! " render_blocks: []\n",
//! " outcome:\n",
//! " kind: none\n",
//! ))?;
//!
//! let mut components = HashMap::new();
//! components.insert(component.id.clone(), component);
//! let mut steps = HashMap::new();
//! steps.insert("say_hello".to_string(), "Say hello to {{project_name}}.".to_string());
//! let mut step_contents = HashMap::new();
//! step_contents.insert("hello".to_string(), steps);
//!
//! let ir = compile(
//! &profile,
//! &components,
//! &binding.project,
//! &EmptyContext(LineageGraph::default()),
//! &step_contents,
//! &SlotContents::default(),
//! )?;
//! assert_eq!(ir["warble_ir_version"], "0.8");
//! # Ok::<(), Box<dyn std::error::Error>>(())
//! ```
//!
//! # Invariants
//!
//! These hold across the whole workspace, not just this crate — they matter to you as an API
//! consumer too, not only to contributors:
//!
//! 1. **If you write your own back-end that consumes the IR `compile` returns, drive it off the
//! three enum fields** — `realization_kind`, `outcome.kind`, `trigger.kind` — never off a
//! component's `id` or `verb` string. Any enum value your back-end can't handle must be a hard
//! error, not a best-effort guess or a silent no-op.
//! 2. **Depending on `warble` alone pulls in no I/O and no `wren` dependency.** `compile` never
//! touches a filesystem or network — you build the [`ContextLoader`] yourself and hand it in —
//! and this crate has no dependency on any semantic-format library. That's what lets you embed
//! it in a native binary, WASM, or another language's bindings without inheriting a dependency
//! graph you didn't ask for. Nothing in this workspace links such a library any more: a host
//! reads its own format and hands the projection over as a prepared-context document.
//! 3. **No DSL in the composition layer** — there's no boolean algebra, expression language, or
//! imperative logic in the profile/IR schema. The one form of conditionality that does exist
//! is a closed vocabulary: a step can set `conditional: true` with a `when` guard whose
//! `guard` is one of `on_failure` / `on_flag` / `on_missing` and whose `target` names what
//! it's checking — never an arbitrary predicate. If you're writing a dispatcher, that means
//! you must either realize `when` or loud-fail on it, not silently treat it as a no-op. IR
//! growth is additive (a new optional field), never a new mechanism.
//! 4. **The IR `compile` emits never names a specific mechanism** (no `cron`, `slack`,
//! `subagent`, …). If you're writing a dispatcher, those names only show up later, at the
//! capability layer via `realize-via` — so the same IR is valid input to any runtime's
//! back-end, including ones this crate has never heard of. See
//! [`capability-model.md`][spec-cap].
//! 5. **This crate builds exactly one native capability: `blast_radius`** (semantic lineage — see
//! [`blast-radius.md`][spec-blast]). If you're checking what a target back-end can support
//! natively versus needs to borrow from its runtime, know that everything else — human
//! approval, VCS/rollback, scheduling, subagent dispatch, schema introspection — is expected
//! to come from the runtime (realize-via), not from `core`.
//!
//! [spec-authoring]: https://github.com/Canner/Warble/blob/main/docs/spec/authoring.md
//! [spec-ir]: https://github.com/Canner/Warble/blob/main/docs/spec/ir-schema.md
//! [spec-cap]: https://github.com/Canner/Warble/blob/main/docs/spec/capability-model.md
//! [spec-blast]: https://github.com/Canner/Warble/blob/main/docs/spec/blast-radius.md
pub use ;
pub use ;
pub use CompileError;
pub use ;
pub use read_raw_dir;
pub use ;