feat(config): load typed ask permissions.toml schema#2491
Conversation
There was a problem hiding this comment.
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.
fe0923b to
fb77cf1
Compare
|
Harvested the typed I also added a small local docs/test follow-up because the current |
|
Thanks @greyfreedom. This ask-only Current |
Summary
Build on #2404 with the next small foundation slice for persistent permissions.
This PR adds config/schema-only support for a sibling
permissions.tomlfile containing typed ask records:Scope
This intentionally only covers schema and loading:
PermissionsTomlpermissions.tomlnext toconfig.tomlConfigStoredecision = "allow"Non-goals
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 --allcargo test -p codewhale-config --all-featurescargo test -p codewhale-execpolicy --all-featurescargo check -p codewhale-cli -p codewhale-app-server --all-featurescargo clippy -p codewhale-config -p codewhale-execpolicy --all-targets --all-features -- -D warningsGreptile Summary
This PR adds schema and loading support for a sibling
permissions.tomlfile 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.PermissionsTomlwith#[serde(deny_unknown_fields)](rejecting typed allow/deny records), loads it next toconfig.tomlviaload_sibling_permissions, and exposes it throughConfigStore::permissions()andConfigStore::permissions_path().ToolAskRulefromcodewhale-configand hardensToolAskRuleitself with#[serde(deny_unknown_fields)]; tests cover happy-path deserialization, rejection ofdecision = "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 assecrets/api_key.txtwhen 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_rulepath comparison (lines 322–328) uses equality, not glob expansion, which conflicts with the glob-style examples shipped inconfig.example.tomlanddocs/CONFIGURATION.md.Important Files Changed
#[serde(deny_unknown_fields)]toToolAskRule; path matching logic is unchanged (equality, not glob) — the documentedsecrets/**example pattern will not match real paths once approval-flow wiring lands.PermissionsToml,load_sibling_permissions, andConfigStore::permissions()/permissions_path()with clean error propagation and good test coverage including the no-config-file edge case.codewhale-execpolicydependency at the correct version to support theToolAskRulere-export.permissions.tomlsibling file with accurate scope limitations; placed correctly in the upgrade notes section.permissions.tomlformat; does not introduce any parsed changes toconfig.tomlitself.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"]Comments Outside Diff (1)
crates/execpolicy/src/lib.rs, line 322-328 (link)The
pathfield is documented as an "Optional file path pattern to match against" (line 86) and the example in bothconfig.example.tomlanddocs/CONFIGURATION.mdshowspath = "secrets/**". However, the matching logic does normalized string equality (normalize_path_value(pattern) == normalize_path_value(path)), not glob expansion. A rule withpath = "secrets/**"will never match a real path likesecrets/api_key.txt; it will only match the exact stringsecrets/**.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.
globorglobset), or the field name/documentation should be updated to reflect that only exact paths are supported.Reviews (2): Last reviewed commit: "feat(config): load typed ask permissions..." | Re-trigger Greptile