use super::{OptionalPackage, Stdlib};
pub struct FilterField {
pub name: &'static str,
pub ty: &'static str,
pub doc: &'static str,
pub normalization: FieldNormalization,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FieldNormalization {
Verbatim,
VfsPath,
RepositoryUrl,
}
impl FieldNormalization {
pub(crate) fn apply(self, value: &str) -> Result<String, String> {
match self {
Self::Verbatim => Ok(value.to_string()),
Self::VfsPath => {
crate::runtime::fs::guest_normalize("/", value).map_err(|error| error.to_string())
}
Self::RepositoryUrl => {
crate::stdlib::git::canonical_url(value).map_err(|error| error.to_string())
}
}
}
}
const fn field(name: &'static str, ty: &'static str, doc: &'static str) -> FilterField {
FilterField {
name,
ty,
doc,
normalization: FieldNormalization::Verbatim,
}
}
const fn vfs_path_field(name: &'static str, doc: &'static str) -> FilterField {
normalized_string_field(name, doc, FieldNormalization::VfsPath)
}
const fn normalized_string_field(
name: &'static str,
doc: &'static str,
normalization: FieldNormalization,
) -> FilterField {
FilterField {
name,
ty: "string",
doc,
normalization,
}
}
pub struct Capability {
pub name: &'static str,
pub main_denial: Option<&'static str>,
pub summary: &'static str,
pub filter_fields: &'static [FilterField],
pub example_filter: &'static str,
}
impl Capability {
pub fn is_template(&self) -> bool {
self.name.contains('<')
}
pub fn grantable_to_main(&self) -> bool {
self.main_denial.is_none()
}
pub fn field_names(&self) -> impl Iterator<Item = &'static str> {
self.filter_fields.iter().map(|f| f.name)
}
}
pub struct CapabilityGroup {
pub module: &'static str,
pub capabilities: &'static [Capability],
}
const PATH: FilterField = vfs_path_field("path", "Normalized absolute VFS path the call targets");
const RECURSIVE: FilterField = field(
"recursive",
"boolean",
"Whether the operation applies recursively",
);
const FROM: FilterField = vfs_path_field("from", "Normalized absolute source VFS path");
const TO: FilterField = vfs_path_field("to", "Normalized absolute destination VFS path");
const CODE_RECURSIVE: FilterField = field(
"recursive",
"boolean",
"Always true; supplied by the code module only",
);
const KEY: FilterField = field("key", "string", "Session key the call targets");
const PREFIX: FilterField = field("prefix", "string", "Session key prefix being listed");
const FS: &[Capability] = &[
Capability {
name: "fs.read",
main_denial: None,
summary: "Read files and code workspace content (including search and ignore rules)",
filter_fields: &[
PATH,
field(
"length",
"number",
"Bytes requested; supplied by `readBytes` only",
),
field(
"chunkSize",
"number",
"Chunk size in bytes; supplied by `bytes` only",
),
CODE_RECURSIVE,
],
example_filter: "path glob \"*.csv\"",
},
Capability {
name: "fs.write",
main_denial: None,
summary: "Create, write, append, or apply code edits to files",
filter_fields: &[
PATH,
field(
"length",
"number",
"Content size in bytes; supplied by `write`, `writeText`, `append`, \
`appendText`, and code edits",
),
field(
"max_bytes",
"number",
"Requested download size cap in bytes; supplied by `http.download` and packages that stream downloads",
),
field(
"diff",
"string",
"Unified diff of the edit; supplied by code edits only",
),
],
example_filter: "path glob \"/out/*\"",
},
Capability {
name: "fs.stat",
main_denial: None,
summary: "Inspect metadata (including code workspace discovery)",
filter_fields: &[PATH, CODE_RECURSIVE],
example_filter: "path glob \"/data/*\"",
},
Capability {
name: "fs.list",
main_denial: None,
summary: "List directory entries (including code search, glob and tree)",
filter_fields: &[PATH, RECURSIVE],
example_filter: "path glob \"/data/*\"",
},
Capability {
name: "fs.mkdir",
main_denial: None,
summary: "Create directories",
filter_fields: &[PATH, RECURSIVE],
example_filter: "path glob \"/tmp/*\"",
},
Capability {
name: "fs.remove",
main_denial: None,
summary: "Delete files or directories",
filter_fields: &[PATH, RECURSIVE],
example_filter: "path glob \"/tmp/*\"",
},
Capability {
name: "fs.move",
main_denial: None,
summary: "Move or rename a path",
filter_fields: &[FROM, TO],
example_filter: "to glob \"/archive/*\"",
},
Capability {
name: "fs.copy",
main_denial: None,
summary: "Copy a path",
filter_fields: &[FROM, TO, RECURSIVE],
example_filter: "to glob \"/backup/*\"",
},
];
const BRANCH: FilterField = field("branch", "string", "Local or requested remote branch name");
const REMOTE: FilterField = normalized_string_field(
"remote",
"Canonical HTTPS repository URL",
FieldNormalization::RepositoryUrl,
);
const REMOTE_NAME: FilterField = field("remoteName", "string", "Named remote, for example origin");
const GIT: &[Capability] = &[
Capability {
name: "git.init",
main_denial: None,
summary: "Create a local repository and its VFS directory",
filter_fields: &[PATH],
example_filter: "path == \"/repo\"",
},
Capability {
name: "git.clone",
main_denial: None,
summary: "Clone an HTTPS repository into a VFS directory",
filter_fields: &[PATH, REMOTE_NAME, REMOTE, BRANCH],
example_filter: "path == \"/repo\" and remote == \"https://github.com/acme/project.git\"",
},
Capability {
name: "git.fetch",
main_denial: None,
summary: "Fetch or pull HTTPS remote branches into an existing repository",
filter_fields: &[PATH, REMOTE_NAME, REMOTE, BRANCH],
example_filter: "path == \"/repo\" and remote == \"https://github.com/acme/project.git\"",
},
Capability {
name: "git.commit",
main_denial: None,
summary: "Commit staged changes with Blueprint identity",
filter_fields: &[PATH, BRANCH],
example_filter: "path == \"/repo\" and branch == \"main\"",
},
];
const HTTP_VERB_FIELDS: &[FilterField] = &[
field("host", "string", "Destination host, without port"),
field("path", "string", "URL path component"),
field("body_size", "number", "Request body size in bytes"),
field("timeout_ms", "number", "Request timeout in milliseconds"),
];
const HTTP: &[Capability] = &[
Capability {
name: "http.get",
main_denial: None,
summary: "HTTP GET",
filter_fields: HTTP_VERB_FIELDS,
example_filter: "host == \"api.example.com\"",
},
Capability {
name: "http.post",
main_denial: None,
summary: "HTTP POST",
filter_fields: HTTP_VERB_FIELDS,
example_filter: "host == \"api.example.com\"",
},
Capability {
name: "http.put",
main_denial: None,
summary: "HTTP PUT",
filter_fields: HTTP_VERB_FIELDS,
example_filter: "host == \"api.example.com\"",
},
Capability {
name: "http.patch",
main_denial: None,
summary: "HTTP PATCH",
filter_fields: HTTP_VERB_FIELDS,
example_filter: "host == \"api.example.com\"",
},
Capability {
name: "http.delete",
main_denial: None,
summary: "HTTP DELETE",
filter_fields: HTTP_VERB_FIELDS,
example_filter: "host == \"api.example.com\"",
},
Capability {
name: "http.head",
main_denial: None,
summary: "HTTP HEAD",
filter_fields: HTTP_VERB_FIELDS,
example_filter: "host == \"api.example.com\"",
},
Capability {
name: "http.options",
main_denial: None,
summary: "HTTP OPTIONS",
filter_fields: HTTP_VERB_FIELDS,
example_filter: "host == \"api.example.com\"",
},
Capability {
name: "http.download",
main_denial: None,
summary: "Download a URL straight to the VFS",
filter_fields: &[
field("host", "string", "Download host, without port"),
field("url_path", "string", "URL path component"),
vfs_path_field(
"vfs_path",
"Normalized absolute destination path in the VFS",
),
field(
"max_bytes",
"number",
"Requested download size cap in bytes",
),
field(
"overwrite",
"boolean",
"Whether an existing file may be clobbered",
),
field(
"decompress",
"boolean",
"Whether the response is decompressed on write",
),
],
example_filter: "host == \"cdn.example.com\" and overwrite == false",
},
];
pub const HTTP_OTHER_METHOD: Capability = Capability {
name: "http.<method>",
main_denial: None,
summary: "Any other HTTP method, through `http.request`: `http.trace` gates TRACE",
filter_fields: HTTP_VERB_FIELDS,
example_filter: "host == \"api.example.com\"",
};
const MCP: &[Capability] = &[Capability {
name: "mcp.<server>",
main_denial: None,
summary: "Call tools on a declared outbound MCP server (streamable_http)",
filter_fields: &[
field("tool", "string", "Name of the MCP tool being called"),
field(
"transport",
"string",
"MCP transport; always \"streamable_http\" today",
),
],
example_filter: "tool == \"create_issue\"",
}];
const EMBEDDING: &[Capability] = &[Capability {
name: "embedding.embed",
main_denial: None,
summary: "Embed text through a declared embedding alias (embed) and enumerate the aliases \
it may use (models). Narrowing `model` also narrows what `models()` reveals: \
every candidate is filtered through this same rule, so a listing never offers \
an alias the caller would be denied at call time. A policy allowing no \
candidates returns an empty listing",
filter_fields: &[
field(
"model",
"string",
"Alias the call targets or the candidate being listed. The runtime preflight \
does not ask policy about an empty name",
),
field(
"input_count",
"number",
"Texts in this call — N for embed, 0 for models. Input text is never in this \
context",
),
],
example_filter: "model glob \"memory-*\"",
}];
const LLM: &[Capability] = &[Capability {
name: "llm.call",
main_denial: None,
summary: "Call a model (call, batch) and enumerate the models it may call (models). \
Narrowing `model` also narrows what `models()` reveals: every candidate is \
filtered through this same rule, so a listing never offers a model the \
caller would be denied at call time. A policy allowing no candidates \
returns an empty listing",
filter_fields: &[
field(
"model",
"string",
"Model name the call targets or the candidate being listed. \
The runtime preflight does not ask policy about an empty name",
),
field(
"prompt_count",
"number",
"Prompts in this dispatch — 1 for call, N for batch, 0 for models. Prompt text \
is never in this context",
),
],
example_filter: "model glob \"claude-*\"",
}];
const SECRETS: &[Capability] = &[Capability {
name: "secrets.get",
main_denial: Some(
"secret values are never available to main-module code, and no policy can \
grant this. The package that needs this credential resolves it internally \
and never returns it — pass the secret NAME to that package's API instead",
),
summary: "Read a Blueprint-declared secret value",
filter_fields: &[field("name", "string", "Declared secret name being read")],
example_filter: "name == \"STRIPE_API_KEY\"",
}];
const SESSION: &[Capability] = &[
Capability {
name: "session.read",
main_denial: None,
summary: "Read session state (get, has), and decide which keys a list may reveal",
filter_fields: &[KEY],
example_filter: "key glob \"triage/*\"",
},
Capability {
name: "session.write",
main_denial: None,
summary: "Store or overwrite a session value (set)",
filter_fields: &[KEY],
example_filter: "key glob \"triage/*\"",
},
Capability {
name: "session.remove",
main_denial: None,
summary: "Delete a session key",
filter_fields: &[KEY],
example_filter: "key glob \"triage/*\"",
},
Capability {
name: "session.list",
main_denial: None,
summary: "Enumerate session keys under a prefix",
filter_fields: &[PREFIX],
example_filter: "prefix == \"triage/\"",
},
];
const CORE_CATALOG: &[CapabilityGroup] = &[
CapabilityGroup {
module: "submilli:embedding",
capabilities: EMBEDDING,
},
CapabilityGroup {
module: "submilli:fs",
capabilities: FS,
},
CapabilityGroup {
module: "submilli:git",
capabilities: GIT,
},
CapabilityGroup {
module: "submilli:http",
capabilities: HTTP,
},
CapabilityGroup {
module: "submilli:llm",
capabilities: LLM,
},
CapabilityGroup {
module: "@mcp",
capabilities: MCP,
},
CapabilityGroup {
module: "submilli:secrets",
capabilities: SECRETS,
},
CapabilityGroup {
module: "submilli:session",
capabilities: SESSION,
},
];
const AGENT: FilterField = field(
"agent",
"string",
"The sub-agent's name, as `submilli:agents` list() names it",
);
const AGENTS: CapabilityGroup = CapabilityGroup {
module: "submilli:agents",
capabilities: &[Capability {
name: "agent.run",
main_denial: None,
summary: "Hand work to a harness sub-agent, or list the agents",
filter_fields: &[AGENT],
example_filter: "agent == \"researcher\"",
}],
};
const SKILL: FilterField = field(
"name",
"string",
"The skill's name, as `submilli:skills` list() names it",
);
const SKILLS: CapabilityGroup = CapabilityGroup {
module: "submilli:skills",
capabilities: &[Capability {
name: "skill.load",
main_denial: None,
summary: "Load a harness skill, read its files, or list the skills",
filter_fields: &[SKILL],
example_filter: "name == \"code-review\"",
}],
};
fn optional_group(package: OptionalPackage) -> &'static CapabilityGroup {
match package {
OptionalPackage::Agents => &AGENTS,
OptionalPackage::Skills => &SKILLS,
}
}
pub fn catalog() -> &'static [CapabilityGroup] {
CORE_CATALOG
}
pub fn catalog_for(stdlib: Stdlib) -> Vec<&'static CapabilityGroup> {
let mut groups: Vec<&'static CapabilityGroup> = CORE_CATALOG.iter().collect();
groups.extend(stdlib.optional_packages().map(optional_group));
groups.sort_by_key(|group| catalog_order(group.module));
groups
}
fn catalog_order(module: &str) -> &str {
module
.strip_prefix("submilli:")
.or_else(|| module.strip_prefix('@'))
.unwrap_or(module)
}
pub(crate) fn all_groups() -> impl Iterator<Item = &'static CapabilityGroup> {
CORE_CATALOG.iter().chain(
OptionalPackage::ALL
.iter()
.map(|package| optional_group(*package)),
)
}
pub fn find(name: &str) -> Option<&'static Capability> {
find_for(Stdlib::core(), name)
}
pub fn find_for(stdlib: Stdlib, name: &str) -> Option<&'static Capability> {
catalog_for(stdlib)
.into_iter()
.flat_map(|group| group.capabilities)
.find(|cap| cap.name == name)
}
pub(crate) fn find_any(name: &str) -> Option<&'static Capability> {
all_groups()
.flat_map(|group| group.capabilities)
.find(|cap| cap.name == name)
}
pub fn uncataloged_http_method(name: &str) -> Option<&str> {
let method = name.strip_prefix("http.")?;
(is_lowercase_http_token(method) && find(name).is_none()).then_some(method)
}
pub fn find_gating(name: &str) -> Option<&'static Capability> {
find_gating_for(Stdlib::core(), name)
}
pub fn find_gating_for(stdlib: Stdlib, name: &str) -> Option<&'static Capability> {
find_for(stdlib, name).or_else(|| uncataloged_http_method(name).map(|_| &HTTP_OTHER_METHOD))
}
fn is_lowercase_http_token(method: &str) -> bool {
!method.is_empty()
&& method.bytes().all(|byte| {
byte.is_ascii_lowercase() || byte.is_ascii_digit() || b"!#$%&'*+-.^_`|~".contains(&byte)
})
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashSet;
#[test]
fn only_secrets_get_is_refused_to_main() {
let refused: Vec<&str> = all_groups()
.flat_map(|group| group.capabilities)
.filter(|cap| !cap.grantable_to_main())
.map(|cap| cap.name)
.collect();
assert_eq!(refused, ["secrets.get"]);
}
#[test]
fn core_set_lists_the_core_catalog() {
let core: Vec<&str> = catalog().iter().map(|group| group.module).collect();
let for_core: Vec<&str> = catalog_for(Stdlib::core())
.iter()
.map(|group| group.module)
.collect();
assert_eq!(for_core, core);
}
#[test]
fn catalog_entries_are_well_formed() {
let mut seen = HashSet::new();
for group in all_groups() {
for cap in group.capabilities {
assert!(
cap.name.contains('.') && !cap.name.is_empty(),
"capability name '{}' must be `module.action`",
cap.name
);
assert!(seen.insert(cap.name), "duplicate capability '{}'", cap.name);
assert!(!cap.summary.is_empty(), "{}: empty summary", cap.name);
assert!(
!cap.example_filter.is_empty(),
"{}: empty example_filter",
cap.name
);
assert!(
cap.main_denial.is_none_or(|reason| !reason.is_empty()),
"{}: empty main_denial reason",
cap.name
);
for f in cap.filter_fields {
assert!(!f.name.is_empty(), "{}: unnamed filter field", cap.name);
assert!(
matches!(f.ty, "string" | "number" | "boolean"),
"{}.{}: unknown field type '{}'",
cap.name,
f.name,
f.ty
);
assert!(!f.doc.is_empty(), "{}.{}: empty doc", cap.name, f.name);
}
}
}
}
#[test]
fn session_group_is_cataloged() {
let group = catalog()
.iter()
.find(|g| g.module == "submilli:session")
.expect("submilli:session group");
let names: Vec<&str> = group.capabilities.iter().map(|cap| cap.name).collect();
assert_eq!(
names,
[
"session.read",
"session.write",
"session.remove",
"session.list"
]
);
assert_eq!(
find("session.list")
.unwrap()
.field_names()
.collect::<Vec<_>>(),
["prefix"]
);
}
#[test]
fn embedding_group_is_cataloged() {
let group = catalog()
.iter()
.find(|g| g.module == "submilli:embedding")
.expect("submilli:embedding group");
let names: Vec<&str> = group.capabilities.iter().map(|cap| cap.name).collect();
assert_eq!(names, ["embedding.embed"]);
let capability = find("embedding.embed").unwrap();
assert_eq!(
capability.field_names().collect::<Vec<_>>(),
["model", "input_count"]
);
assert_eq!(capability.example_filter, "model glob \"memory-*\"");
assert!(capability.main_denial.is_none());
assert!(
capability.summary.contains("models()"),
"the summary must say narrowing `model` narrows discovery: {}",
capability.summary
);
}
#[test]
fn llm_group_is_cataloged() {
let group = catalog()
.iter()
.find(|g| g.module == "submilli:llm")
.expect("submilli:llm group");
let names: Vec<&str> = group.capabilities.iter().map(|cap| cap.name).collect();
assert_eq!(names, ["llm.call"]);
assert_eq!(
find("llm.call").unwrap().field_names().collect::<Vec<_>>(),
["model", "prompt_count"]
);
let summary = find("llm.call").unwrap().summary;
assert!(
summary.contains("models()"),
"the summary must say narrowing `model` narrows discovery: {summary}"
);
}
#[test]
fn uncataloged_http_methods_fill_the_template() {
assert_eq!(uncataloged_http_method("http.trace"), Some("trace"));
assert_eq!(uncataloged_http_method("http.propfind"), Some("propfind"));
for name in [
"http.get",
"http.<method>",
"http.TRACE",
"http.",
"http.a b",
"fs.trace",
] {
assert!(uncataloged_http_method(name).is_none(), "{name}");
}
assert!(HTTP_OTHER_METHOD.is_template());
assert_eq!(
find_gating("http.trace").map(|c| c.name),
Some("http.<method>")
);
assert_eq!(find_gating("http.get").map(|c| c.name), Some("http.get"));
}
#[test]
fn find_and_is_template() {
assert_eq!(find("fs.read").map(|c| c.name), Some("fs.read"));
assert!(find("fs.nope").is_none());
assert!(find("mcp.<server>").is_some_and(Capability::is_template));
assert!(!find("fs.read").unwrap().is_template());
}
}