Skip to content

feat(config): load typed ask permissions.toml schema#2491

Closed
greyfreedom wants to merge 1 commit into
Hmbown:mainfrom
greyfreedom:feat/permissions-toml-typed-ask-schema
Closed

feat(config): load typed ask permissions.toml schema#2491
greyfreedom wants to merge 1 commit into
Hmbown:mainfrom
greyfreedom:feat/permissions-toml-typed-ask-schema

Conversation

@greyfreedom

@greyfreedom greyfreedom commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Summary

Build on #2404 with the next small foundation slice for persistent permissions.

This PR adds config/schema-only support for a sibling permissions.toml file containing typed ask records:

[[rules]]
tool = "exec_shell"
command = "cargo test"

[[rules]]
tool = "read_file"
path = "secrets/**"

Scope

This intentionally only covers schema and loading:

  • add PermissionsToml
  • load permissions.toml next to config.toml
  • expose loaded rules through ConfigStore
  • document the ask-only schema
  • reject typed allow/deny-style records such as decision = "allow"

Non-goals

  • no typed allow/deny records
  • no approval-flow wiring
  • no TUI approval persistence UI
  • no changes to existing allow/deny behavior

Context

This is a smaller follow-up to the broader reference PR #2242. The typed ask execution-policy foundation landed in #2404, and this PR adds only the persistence schema/loading layer requested as the next safer slice.

Related discussion:

Validation

  • cargo fmt --all
  • cargo test -p codewhale-config --all-features
  • cargo test -p codewhale-execpolicy --all-features
  • cargo check -p codewhale-cli -p codewhale-app-server --all-features
  • cargo clippy -p codewhale-config -p codewhale-execpolicy --all-targets --all-features -- -D warnings

Greptile Summary

This PR adds schema and loading support for a sibling permissions.toml file containing typed ask rules, building on the execpolicy foundation from #2404. It intentionally stays in schema/loading scope — no approval-flow wiring or UI persistence.

  • Introduces PermissionsToml with #[serde(deny_unknown_fields)] (rejecting typed allow/deny records), loads it next to config.toml via load_sibling_permissions, and exposes it through ConfigStore::permissions() and ConfigStore::permissions_path().
  • Re-exports ToolAskRule from codewhale-config and hardens ToolAskRule itself with #[serde(deny_unknown_fields)]; tests cover happy-path deserialization, rejection of decision = "allow" records, and the no-config-file edge case.

Confidence Score: 4/5

Safe to merge as a schema/loading-only slice; the unresolved path-matching issue will silently misfire ask rules once approval-flow wiring lands.

The loading logic and schema are correct, and the test coverage is solid. The path field does string equality against a normalized value — documented examples like path = "secrets/**" will never match real paths such as secrets/api_key.txt when the approval-flow consumer arrives. This is a pre-existing defect carried forward and now further embedded by the documentation and examples in this PR.

crates/execpolicy/src/lib.rs — matching_ask_rule path comparison (lines 322–328) uses equality, not glob expansion, which conflicts with the glob-style examples shipped in config.example.toml and docs/CONFIGURATION.md.

Important Files Changed

Filename Overview
crates/execpolicy/src/lib.rs Adds #[serde(deny_unknown_fields)] to ToolAskRule; path matching logic is unchanged (equality, not glob) — the documented secrets/** example pattern will not match real paths once approval-flow wiring lands.
crates/config/src/lib.rs Adds PermissionsToml, load_sibling_permissions, and ConfigStore::permissions()/permissions_path() with clean error propagation and good test coverage including the no-config-file edge case.
crates/config/Cargo.toml Adds codewhale-execpolicy dependency at the correct version to support the ToolAskRule re-export.
docs/CONFIGURATION.md Documents the new permissions.toml sibling file with accurate scope limitations; placed correctly in the upgrade notes section.
config.example.toml Adds a comment-only block showing the permissions.toml format; does not introduce any parsed changes to config.toml itself.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["ConfigStore::load(path)"] --> B["resolve_config_path()"]
    B --> C{"config.toml exists?"}
    C -- yes --> D["fs::read_to_string\n+ toml::from_str → ConfigToml"]
    C -- no --> E["ConfigToml::default()"]
    D --> F["load_sibling_permissions(config_path)"]
    E --> F
    F --> G["permissions_path_for_config_path()\n→ config_dir/permissions.toml"]
    G --> H{"permissions.toml exists?"}
    H -- no --> I["PermissionsToml::default()"]
    H -- yes --> J["fs::read_to_string\n+ toml::from_str → PermissionsToml"]
    J --> K{"parse ok?"}
    K -- no --> L["Err: propagated to caller"]
    K -- yes --> M["PermissionsToml { rules: Vec<ToolAskRule> }"]
    I --> N["ConfigStore { path, config, permissions }"]
    M --> N
    N --> O["ConfigStore::permissions() → &PermissionsToml"]
    N --> P["ConfigStore::permissions_path() → PathBuf"]
Loading

Comments Outside Diff (1)

  1. crates/execpolicy/src/lib.rs, line 322-328 (link)

    P1 Path field does equality matching, not glob matching

    The path field is documented as an "Optional file path pattern to match against" (line 86) and the example in both config.example.toml and docs/CONFIGURATION.md shows path = "secrets/**". However, the matching logic does normalized string equality (normalize_path_value(pattern) == normalize_path_value(path)), not glob expansion. A rule with path = "secrets/**" will never match a real path like secrets/api_key.txt; it will only match the exact string secrets/**.

    When the approval-flow wiring arrives in a follow-up PR, users who followed the documented examples will find their ask-rules silently never fire on any real file path. Either the matching should use a glob library (e.g. glob or globset), or the field name/documentation should be updated to reflect that only exact paths are supported.

    Fix in Codex Fix in Claude Code Fix in Cursor

Reviews (2): Last reviewed commit: "feat(config): load typed ask permissions..." | Re-trigger Greptile

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new sibling configuration file, permissions.toml, which defines ask-only typed permission rules (ToolAskRule) loaded alongside config.toml. It adds the PermissionsToml schema, integrates its loading into ConfigStore, enforces deny_unknown_fields on ToolAskRule to reject unsupported allow/deny fields, and provides corresponding tests and documentation. There are no review comments, and I have no feedback to provide.

@greyfreedom greyfreedom force-pushed the feat/permissions-toml-typed-ask-schema branch from fe0923b to fb77cf1 Compare June 1, 2026 10:41
@Hmbown

Hmbown commented Jun 1, 2026

Copy link
Copy Markdown
Owner

Harvested the typed permissions.toml loading slice into codex/v0.8.50-triage. Thank you for carving this out from the larger permissions work.

I also added a small local docs/test follow-up because the current ToolAskRule matching is exact-path matching, not glob matching; the example now uses a literal secrets/api_key.txt path instead of secrets/**. The broader persistent-permissions UI/API work can stay in #2242.

@Hmbown

Hmbown commented Jun 5, 2026

Copy link
Copy Markdown
Owner

Thanks @greyfreedom. This ask-only permissions.toml schema/loading slice has already been harvested as 3df018994, with maintainer follow-up 69cff9375 to keep the documented path example exact until glob matching lands.

Current codex/v0.9.0-stewardship includes PermissionsToml, sibling permissions.toml loading through ConfigStore, ToolAskRule unknown-field rejection, docs/config examples, and tests for deserialization, allow/deny rejection, sibling loading, and permissions-only loading. Closing this PR as harvested/superseded; the broader persistent-permissions work remains tracked in #1186 / #2242.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants