1use serde::{Deserialize, Serialize};
11use std::borrow::Cow;
12use std::collections::HashMap;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
16#[serde(rename_all = "kebab-case")]
17pub enum CapabilityOutcome {
18 Native,
19 RealizeVia,
20 Degrade,
21 Fail,
22}
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
26#[serde(rename_all = "lowercase")]
27pub enum ProvidedBy {
28 Runtime,
29 Warble,
30 None,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
36#[serde(rename_all = "kebab-case")]
37pub enum Criticality {
38 SafetyCritical,
39 Required,
40 BestEffort,
41}
42
43pub(crate) const CONTEXT_ISOLATION_CAPABILITY: &str = "context_isolation";
47
48#[derive(Debug, Clone)]
49pub struct CapabilityEntry {
50 pub outcome: CapabilityOutcome,
51 pub via: Option<Cow<'static, str>>,
55 pub provided_by: ProvidedBy,
56 pub criticality: Criticality,
57 pub note: Option<Cow<'static, str>>,
58}
59
60pub type CapabilityProfile = HashMap<String, CapabilityEntry>;
61
62fn profile(
65 entries: impl IntoIterator<Item = (&'static str, CapabilityEntry)>,
66) -> CapabilityProfile {
67 entries
68 .into_iter()
69 .map(|(k, v)| (k.to_string(), v))
70 .collect()
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
76pub enum TargetId {
77 Headless,
78 Interactive,
79 CodexInteractive,
80 PiInteractive,
81}
82
83impl TargetId {
84 pub fn as_str(&self) -> &'static str {
85 match self {
86 TargetId::Headless => "claude-code:headless",
87 TargetId::Interactive => "claude-code:interactive",
88 TargetId::CodexInteractive => "codex:interactive",
89 TargetId::PiInteractive => "pi:interactive",
90 }
91 }
92
93 pub fn mode(&self) -> &'static str {
96 match self {
97 TargetId::Headless => "headless",
98 TargetId::Interactive => "interactive",
99 TargetId::CodexInteractive => "interactive",
103 TargetId::PiInteractive => "interactive",
105 }
106 }
107
108 pub fn parse(value: &str) -> Option<TargetId> {
109 match value {
110 "claude-code:headless" => Some(TargetId::Headless),
111 "claude-code:interactive" => Some(TargetId::Interactive),
112 "codex:interactive" => Some(TargetId::CodexInteractive),
113 "pi:interactive" => Some(TargetId::PiInteractive),
114 _ => None,
115 }
116 }
117
118 pub fn profile(&self) -> CapabilityProfile {
119 match self {
120 TargetId::Headless => headless_profile(),
121 TargetId::Interactive => interactive_profile(),
122 TargetId::CodexInteractive => codex_interactive_profile(),
123 TargetId::PiInteractive => pi_interactive_profile(),
124 }
125 }
126}
127
128pub const DEFAULT_TARGET: TargetId = TargetId::Headless;
130
131pub fn is_known_target(value: &str) -> bool {
132 TargetId::parse(value).is_some()
133}
134
135pub fn known_target_names() -> [&'static str; 4] {
136 [
137 TargetId::Headless.as_str(),
138 TargetId::Interactive.as_str(),
139 TargetId::CodexInteractive.as_str(),
140 TargetId::PiInteractive.as_str(),
141 ]
142}
143
144fn pi_interactive_profile() -> CapabilityProfile {
150 use CapabilityOutcome::*;
151 use Criticality::*;
152 use ProvidedBy::{Runtime, Warble};
153 profile([
154 (
155 "component_invocation",
156 entry(
157 Fail,
158 None,
159 ProvidedBy::None,
160 Required,
161 Some("pi interactive emits discovery artifacts and has no invocation runtime"),
162 ),
163 ),
164 ("sql_execution:read_only", entry(RealizeVia, Some("native-session-mcp"), Runtime, Required, Some("the host MCP server's query tool; pi has no shell"))),
165 ("genbi_build", entry(RealizeVia, Some("native-session-mcp"), Runtime, Required, Some("the host MCP server's dashboard save tool"))),
166 ("semantic_introspection", entry(Fail, None, ProvidedBy::None, Required, Some("a pi session has no repository read and the host MCP server exposes no introspection tool"))),
167 ("raw_material_read", entry(Fail, None, ProvidedBy::None, Required, Some("a pi session has no repository read"))),
168 ("source_connect", entry(Fail, None, ProvidedBy::None, Required, Some("setup is not realized on pi:interactive"))),
169 ("llm:strong", entry(Native, None, Runtime, Required, None)),
170 ("llm:cheap", entry(Native, None, Runtime, Required, None)),
171 ("llm:per_step_tier", entry(Degrade, Some("single-session-model"), Runtime, Required, Some("every step runs on the one session model; a host plan (--native-host) restores exact tiers by running the steps itself"))),
172 ("context_write_authz", entry(Fail, None, ProvidedBy::None, SafetyCritical, Some("pi has no approval channel"))),
173 ("context_validate", entry(Fail, None, ProvidedBy::None, Required, Some("context enrichment is not realized on pi:interactive"))),
174 ("context_build", entry(Fail, None, ProvidedBy::None, Required, Some("context enrichment is not realized on pi:interactive"))),
175 ("version_control", entry(Fail, None, ProvidedBy::None, Required, Some("a pi session has no shell"))),
176 ("human_approval", entry(Fail, None, ProvidedBy::None, SafetyCritical, Some("pi has no per-tool approval"))),
177 ("enrichment_apply:deterministic", entry(Fail, None, ProvidedBy::None, SafetyCritical, Some("no human-approval gate on pi:interactive"))),
178 ("render_contract", entry(Degrade, Some("terminal-markdown"), Runtime, BestEffort, None)),
179 ("artifact_write", entry(RealizeVia, Some("session-scoped-artifact-api"), Runtime, SafetyCritical, Some("the host owns the artifact API and persistence"))),
180 ("blast_radius", entry(Fail, None, Warble, SafetyCritical, Some("requires fine_grained_binding"))),
181 ])
182}
183
184fn entry(
185 outcome: CapabilityOutcome,
186 via: Option<&'static str>,
187 provided_by: ProvidedBy,
188 criticality: Criticality,
189 note: Option<&'static str>,
190) -> CapabilityEntry {
191 CapabilityEntry {
192 outcome,
193 via: via.map(Cow::Borrowed),
194 provided_by,
195 criticality,
196 note: note.map(Cow::Borrowed),
197 }
198}
199
200fn headless_profile() -> CapabilityProfile {
201 use CapabilityOutcome::*;
202 use Criticality::*;
203 use ProvidedBy::{Runtime, Warble};
204 profile([
205 (
206 "component_invocation",
207 entry(
208 Fail,
209 None,
210 ProvidedBy::None,
211 Required,
212 Some("no trusted component-invocation handler is installed for this file target"),
213 ),
214 ),
215 (
216 "sql_execution:read_only",
217 entry(Native, Some("bash-wren"), Runtime, Required, None),
218 ),
219 (
220 "genbi_build",
221 entry(Native, Some("bash-wren"), Runtime, Required, None),
222 ),
223 (
224 "semantic_introspection",
225 entry(RealizeVia, Some("bash-wren"), Runtime, Required, None),
226 ),
227 (
230 "schema_introspection",
231 entry(RealizeVia, Some("bash-wren"), Runtime, Required, None),
232 ),
233 ("llm:strong", entry(Native, None, Runtime, Required, None)),
234 ("llm:cheap", entry(Native, None, Runtime, Required, None)),
235 (
236 "llm:per_step_tier",
237 entry(RealizeVia, Some("subagents"), Runtime, Required, None),
238 ),
239 (
246 "llm:per_step_provider",
247 entry(
248 RealizeVia,
249 Some("bash-script|mcp-server"),
250 Warble,
251 Required,
252 Some("local step via a bash-script script or an mcp-server (warble mcp-serve); cloud steps are the driver's own wren work"),
253 ),
254 ),
255 (
256 "render_contract",
257 entry(RealizeVia, Some("html-file"), Runtime, BestEffort, None),
258 ),
259 (
260 "structured_output_capture",
261 entry(Native, Some("stream-json"), Runtime, Required, None),
262 ),
263 (
264 "human_approval",
265 entry(
266 Fail,
267 None,
268 ProvidedBy::None,
269 SafetyCritical,
270 Some("no human in the loop in headless mode"),
271 ),
272 ),
273 (
274 "write_authz",
275 entry(RealizeVia, Some("fs"), Runtime, SafetyCritical, None),
276 ),
277 (
281 "context_write_authz",
282 entry(RealizeVia, Some("scoped-fs"), Runtime, SafetyCritical, None),
283 ),
284 (
285 "artifact_write",
286 entry(RealizeVia, Some("fs"), Runtime, SafetyCritical, None),
287 ),
288 (
293 CONTEXT_ISOLATION_CAPABILITY,
294 entry(RealizeVia, Some("subagent"), Runtime, Required, None),
295 ),
296 (
301 "scheduler",
302 entry(RealizeVia, Some("os-cron"), Runtime, Required, None),
303 ),
304 (
305 "event_bus",
306 entry(RealizeVia, Some("pub-sub"), Runtime, Required, None),
307 ),
308 (
309 "notify_channel",
310 entry(RealizeVia, Some("mcp-notify"), Runtime, Required, None),
311 ),
312 (
313 "blast_radius",
314 entry(
315 Fail,
316 None,
317 Warble,
318 SafetyCritical,
319 Some("requires fine_grained_binding"),
320 ),
321 ),
322 (
325 "version_control",
326 entry(RealizeVia, Some("git"), Runtime, Required, None),
327 ),
328 ])
329}
330
331fn interactive_profile() -> CapabilityProfile {
332 use CapabilityOutcome::*;
333 use Criticality::*;
334 use ProvidedBy::{Runtime, Warble};
335 profile([
336 (
337 "component_invocation",
338 entry(
339 Fail,
340 None,
341 ProvidedBy::None,
342 Required,
343 Some("no trusted component-invocation handler is installed for this file target"),
344 ),
345 ),
346 (
347 "sql_execution:read_only",
348 entry(Native, Some("bash-wren"), Runtime, Required, None),
349 ),
350 (
351 "genbi_build",
352 entry(Native, Some("bash-wren"), Runtime, Required, None),
353 ),
354 (
355 "semantic_introspection",
356 entry(RealizeVia, Some("bash-wren"), Runtime, Required, None),
357 ),
358 (
359 "source_connect",
360 entry(RealizeVia, Some("native-interactive-command"), Runtime, Required, None),
361 ),
362 (
364 "schema_introspection",
365 entry(RealizeVia, Some("bash-wren"), Runtime, Required, None),
366 ),
367 ("raw_material_read", entry(Native, Some("Read"), Runtime, Required, Some("Claude Code's cwd-scoped Read tool; prompts forbid credentials and raw excerpts"))),
368 ("context_validate", entry(RealizeVia, Some("Bash(wren:*)"), Runtime, Required, None)),
369 ("context_build", entry(RealizeVia, Some("Bash(wren:*)"), Runtime, Required, None)),
370 ("enrichment_apply:deterministic", entry(RealizeVia, Some("native-interactive-approved-tool"), Runtime, SafetyCritical, Some("only after the native human-approval gate; Warble does not run it"))),
371 ("llm:strong", entry(Native, None, Runtime, Required, None)),
372 ("llm:cheap", entry(Native, None, Runtime, Required, None)),
373 (
374 "llm:per_step_tier",
375 entry(RealizeVia, Some("subagents"), Runtime, Required, None),
376 ),
377 (
380 "llm:per_step_provider",
381 entry(
382 RealizeVia,
383 Some("bash-script|mcp-server"),
384 Warble,
385 Required,
386 Some("local step via an emitted local-inference script (Bash); cloud steps stay native subagents"),
387 ),
388 ),
389 (
390 "render_contract",
391 entry(
392 Degrade,
393 Some("terminal-markdown"),
394 Runtime,
395 BestEffort,
396 None,
397 ),
398 ),
399 (
400 "structured_output_capture",
401 entry(
402 Degrade,
403 None,
404 ProvidedBy::None,
405 BestEffort,
406 Some("not captured in the interactive TUI"),
407 ),
408 ),
409 (
410 "human_approval",
411 entry(Native, None, Runtime, SafetyCritical, None),
412 ),
413 (
414 "write_authz",
415 entry(RealizeVia, Some("fs"), Runtime, SafetyCritical, None),
416 ),
417 (
420 "context_write_authz",
421 entry(RealizeVia, Some("scoped-fs"), Runtime, SafetyCritical, None),
422 ),
423 (
424 "artifact_write",
425 entry(RealizeVia, Some("fs"), Runtime, SafetyCritical, None),
426 ),
427 (
432 CONTEXT_ISOLATION_CAPABILITY,
433 entry(RealizeVia, Some("subagent"), Runtime, Required, None),
434 ),
435 (
439 "scheduler",
440 entry(RealizeVia, Some("os-cron"), Runtime, Required, None),
441 ),
442 (
443 "event_bus",
444 entry(RealizeVia, Some("pub-sub"), Runtime, Required, None),
445 ),
446 (
447 "notify_channel",
448 entry(RealizeVia, Some("mcp-notify"), Runtime, Required, None),
449 ),
450 (
451 "blast_radius",
452 entry(
453 Fail,
454 None,
455 Warble,
456 SafetyCritical,
457 Some("requires fine_grained_binding"),
458 ),
459 ),
460 (
463 "version_control",
464 entry(RealizeVia, Some("git"), Runtime, Required, None),
465 ),
466 ])
467}
468
469fn codex_interactive_profile() -> CapabilityProfile {
472 use CapabilityOutcome::*;
473 use Criticality::*;
474 use ProvidedBy::{Runtime, Warble};
475 profile([
476 (
477 "component_invocation",
478 entry(
479 Fail,
480 None,
481 ProvidedBy::None,
482 Required,
483 Some("codex interactive emits discovery artifacts and has no invocation runtime"),
484 ),
485 ),
486 ("sql_execution:read_only", entry(RealizeVia, Some("native-interactive-command"), Runtime, Required, None)),
487 ("genbi_build", entry(RealizeVia, Some("native-interactive-command"), Runtime, Required, None)),
488 ("source_connect", entry(RealizeVia, Some("native-interactive-command"), Runtime, Required, None)),
489 ("semantic_introspection", entry(RealizeVia, Some("codex-repository-read"), Runtime, Required, None)),
490 ("raw_material_read", entry(RealizeVia, Some("codex-repository-read"), Runtime, Required, Some("repository-scoped native TUI reads only; prompts forbid credentials and raw excerpts"))),
491 ("llm:strong", entry(Native, None, Runtime, Required, None)),
492 ("llm:cheap", entry(Native, None, Runtime, Required, None)),
493 ("llm:per_step_tier", entry(RealizeVia, Some("native-interactive-subagents"), Runtime, Required, None)),
494 ("context_write_authz", entry(RealizeVia, Some("native-interactive-human-approval"), Runtime, SafetyCritical, None)),
495 ("context_validate", entry(RealizeVia, Some("native-interactive-command"), Runtime, Required, None)),
496 ("context_build", entry(RealizeVia, Some("native-interactive-command"), Runtime, Required, None)),
497 ("version_control", entry(RealizeVia, Some("git"), Runtime, Required, None)),
498 ("human_approval", entry(Native, Some("native-interactive-human"), Runtime, SafetyCritical, None)),
499 ("enrichment_apply:deterministic", entry(RealizeVia, Some("native-interactive-approved-tool"), Runtime, SafetyCritical, Some("only after the native human-approval gate; Warble does not run it"))),
500 ("render_contract", entry(Degrade, Some("terminal-markdown"), Runtime, BestEffort, None)),
501 ("artifact_write", entry(RealizeVia, Some("session-scoped-artifact-api"), Runtime, SafetyCritical, Some("the host owns the artifact API and persistence"))),
502 ("blast_radius", entry(Fail, None, Warble, SafetyCritical, Some("requires fine_grained_binding"))),
503 ])
504}