Skip to content

feat(types): add ToolApprovalClass + tool_classifier (no behavior change yet)#3092

Merged
houko merged 2 commits into
mainfrom
feat/approval-tool-class
Apr 25, 2026
Merged

feat(types): add ToolApprovalClass + tool_classifier (no behavior change yet)#3092
houko merged 2 commits into
mainfrom
feat/approval-tool-class

Conversation

@houko

@houko houko commented Apr 25, 2026

Copy link
Copy Markdown
Contributor

Why

Today every gated tool funnels through one approval path with the same prompt UX, regardless of whether the tool is reading a workspace file or rebooting the kernel. That's been fine while the tool surface was small, but it forces an all-or-nothing policy and gives operators no leverage to auto-allow safe reads while still gating execs and control-plane changes.

This PR lays the foundation for a tiered approval ladder by introducing a single source of truth for what kind of risk a tool represents. It is purely additive: no caller is wired up yet, and ApprovalManager is intentionally untouched.

Modeled after openclaw's AcpApprovalClass (7 classes, severity-ordered).

What changes

  • New enum librefang_types::tool_class::ToolApprovalClass with 7 variants (ReadonlyScoped, ReadonlySearch, Mutating, ExecCapable, ControlPlane, Interactive, Unknown).
    • Display, Default (-> Unknown), serde snake_case.
    • severity_rank() -> u8 returning 0..=6 (least-to-most cautious).
    • from_snake_case(&str) -> Option<Self> for round-tripping explicit annotations.
  • New helper librefang_runtime::tool_classifier::classify_tool(name, definition) -> ToolApprovalClass.
    • Pattern-matches on tool name (file_read | glob | grep | ls | cat -> ReadonlyScoped, etc.).
    • If the supplied ToolDefinition::input_schema carries an x-tool-class (or nested metadata.tool_class) string and it parses, that wins over the name heuristic. Unrecognized values fall back to the name match — they don't poison the result.
    • ToolDefinition itself is not modified, so the 30+ existing struct-init sites stay source-compatible.
  • Re-exports added so future PRs can write use librefang_types::ToolApprovalClass; and use librefang_runtime::classify_tool;.
  • Unit tests cover: every name pattern, the (file_read, None) case, explicit metadata override, severity ordering, and serde_json round-trip producing "readonly_scoped".

Out of scope (deliberately)

  • No changes to ApprovalManager in crates/librefang-kernel/src/approval.rs.
  • No config schema for class overrides.
  • No escalation ladder, no auto-approve rules.
  • No existing tool's behavior or routing changed.

These will land in follow-up PRs that consume this classifier.

Files touched

  • crates/librefang-types/src/tool_class.rs (new)
  • crates/librefang-types/src/lib.rs (module + re-export)
  • crates/librefang-runtime/src/tool_classifier.rs (new)
  • crates/librefang-runtime/src/lib.rs (module + re-export)

Validation: gates intentionally not run locally per user instruction; relying on CI to verify build/test/clippy.

@github-actions github-actions Bot added size/L 250-999 lines changed area/runtime Agent loop, LLM drivers, WASM sandbox ready-for-review PR is ready for maintainer review labels Apr 25, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8d1c0cbeaa

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +22 to +23
if let Some(explicit) = explicit_class_from_schema(&def.input_schema) {
return explicit;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Prevent untrusted schema from downgrading class

classify_tool returns the class from input_schema metadata before considering the tool name, so any x-tool-class / metadata.tool_class value can lower the risk class. MCP tool definitions are built from server-provided schema (McpConnection::register_tool stores remote input_schema directly in ToolDefinition), so a remote tool can self-label as readonly_scoped and potentially bypass stricter approval tiers once this classifier is consumed by gating logic. Restrict this override to trusted/local definitions or only allow metadata to increase severity.

Useful? React with 👍 / 👎.


fn classify_by_name(name: &str) -> ToolApprovalClass {
match name {
"file_read" | "glob" | "grep" | "ls" | "cat" => ToolApprovalClass::ReadonlyScoped,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include file_list in read-only classification map

The read-only name match omits the canonical built-in file_list, even though aliases like glob, grep, and ls are normalized to file_list (tool_compat::map_tool_name) and builtin_tool_definitions exposes file_list as the directory-read tool. If callers classify normalized or built-in names, file_list falls through to Unknown instead of ReadonlyScoped, which will over-gate harmless listing operations and undermine the intended tiered policy behavior.

Useful? React with 👍 / 👎.

@houko
houko merged commit abf1afb into main Apr 25, 2026
@houko
houko deleted the feat/approval-tool-class branch April 25, 2026 07:20
@github-actions github-actions Bot removed the ready-for-review PR is ready for maintainer review label Apr 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/runtime Agent loop, LLM drivers, WASM sandbox size/L 250-999 lines changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant