Skip to content

feat(rpc): impl config/set with in-memory mutation (#228)#286

Merged
Destynova2 merged 1 commit intomainfrom
feat/rpc-config-228
Apr 26, 2026
Merged

feat(rpc): impl config/set with in-memory mutation (#228)#286
Destynova2 merged 1 commit intomainfrom
feat/rpc-config-228

Conversation

@Destynova2
Copy link
Copy Markdown
Contributor

Summary

Replaces the TODO(#228) stub in server::rpc::config_ns::set with a working in-memory mutation. Mirrors the validated MCP path (server::mcp_handlers::config::apply_config_update) but without disk persistence — that remains explicitly out of scope per #228 ("Non-goal: persistence in config.toml").

This is P1 of the cli-forge-chef brigade plan (obsidian/grob Phase #228). The remaining stubs follow the same pattern in P2/P3/P4:

  • P2 — tools_ns::enable / tools_ns::disable
  • P3 — pledge_ns::set / pledge_ns::clear
  • P4 — hit_ns::set_policy / hit_ns::resolve

Behaviour

  1. Clone the active config snapshot so a validation failure leaves the running registry untouched.
  2. Reject denied sections/keys (providers, dlp, any api_key) via the existing config_guard::is_section_or_key_denied.
  3. Validate dotted keys (section.field) and value types against the target field; surface failures as INVALID_PARAMS_CODE.
  4. On success, build a new Router + ProviderRegistry from the mutated config and atomic-swap the ReloadableState (same primitive used by config_guard::reload_state). In-flight requests finish on the old snapshot.

Errors documented

  • ERR_FORBIDDEN when the caller is below Admin.
  • INVALID_PARAMS_CODE for malformed keys, denied targets, or wrong value types.
  • ERR_INTERNAL when the registry rebuild or atomic swap fails.

Tests

15/15 unit tests covering:

  • set_router_default_succeeds_for_admin
  • set_cache_ttl_succeeds
  • set_denies_for_observer (role check)
  • set_rejects_providers_section (deny-list)
  • set_rejects_dlp_section
  • set_rejects_api_key_field
  • set_rejects_malformed_key
  • set_rejects_unknown_section
  • set_rejects_unknown_router_key
  • set_rejects_wrong_value_type
    • 5 existing resolve_dotted_path* tests

Test plan

  • cargo nextest run -E 'test(config_ns)' --features mcp15/15 passed
  • cargo fmt --all -- --check clean
  • cargo clippy --all-features -- -D warnings clean
  • CI: full nextest + clippy + fmt + audit + deny

Note

The implementation was drafted by commis-config@grob-228 agent during the cli-forge-chef brigade run. The brigade's atomic-swap of the worktree (a maitre-d'hotel rebase quirk) reset the local commit before push; the commit was recovered from the orphan ref and pushed from the main worktree. The code itself is unchanged from what the commis produced and verified.

🤖 Generated with Claude Code

Replace the `TODO(#228)` stub in `server::rpc::config_ns::set` with a
working in-memory mutation, mirroring the validated MCP path
(`server::mcp_handlers::config::apply_config_update`) but without disk
persistence — that remains explicitly out of scope per #228 ("Non-goal:
persistence in config.toml").

Behaviour:
- Clone the active config snapshot so validation failures leave the
  running registry untouched.
- Reject denied sections/keys (`providers`, `dlp`, any `api_key`) via
  the existing `config_guard::is_section_or_key_denied`.
- Validate dotted keys (`section.field`) and value types against the
  target field; surface failures as `INVALID_PARAMS_CODE`.
- On success, build a new `Router` + `ProviderRegistry` from the mutated
  config and atomic-swap the `ReloadableState` (same primitive used by
  `config_guard::reload_state`). In-flight requests finish on the old
  snapshot.

# Errors

- `ERR_FORBIDDEN` when the caller is below `Admin`.
- `INVALID_PARAMS_CODE` for malformed keys, denied targets, or wrong
  value types.
- `ERR_INTERNAL` when the registry rebuild or atomic swap fails.

Tests: 15/15 unit tests pass (success paths for router/cache, denial
paths for providers/dlp/api_key, malformed-key rejection, unknown
section/key, role denial for Observer).

Implements P1 of the cli-forge-chef brigade plan for issue #228.
The remaining stubs (tools_ns enable/disable, pledge_ns set/clear,
hit_ns set_policy/resolve) follow the same pattern in P2/P3/P4.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Destynova2 Destynova2 enabled auto-merge April 26, 2026 16:27
@Destynova2 Destynova2 merged commit 7ae39bb into main Apr 26, 2026
42 checks passed
@Destynova2 Destynova2 deleted the feat/rpc-config-228 branch April 26, 2026 16:34
Destynova2 pushed a commit that referenced this pull request Apr 26, 2026
…228)

Replaces the two `TODO(#228)` stubs in `server::rpc::hit_ns::set_policy`
and `resolve` with working implementations. Closes the last of the four
RPC namespaces tracked by issue #228.

set_policy(name, policy_json):
- Deserializes the JSON payload into `PolicyConfig`.
- The `name` argument always wins over `policy.name` (path-vs-payload).
- Upserts in `config.policies` (replace if same name, append otherwise).
- Compiles a fresh `PolicyMatcher` BEFORE the swap so a malformed glob
  is rejected without dirtying the running registry.
- Atomic-swaps the ReloadableState; the matcher is rebuilt by
  `ReloadableState::new`, so the new policy is live before the RPC
  returns.

resolve(context):
- Builds a `RequestContext` field-by-field from a JSON object (the type
  has no Deserialize derive — it is constructed imperatively in the
  dispatch path; we keep parity here).
- Missing fields fall back to permissive defaults so a partial probe
  ("just match on model") still works.
- Calls `PolicyMatcher::evaluate` and projects the merged
  `ResolvedPolicy` to JSON for transport.

Both functions are gated on `#[cfg(feature = "policies")]` with a
graceful no-op fallback when the feature is off.

# Errors

- `ERR_FORBIDDEN` when the caller is below `Admin` (set_policy) or
  `Operator` (resolve).
- `INVALID_PARAMS_CODE` for empty name, malformed policy JSON,
  malformed glob in match_rules, non-object context.
- `ERR_INTERNAL` when the registry rebuild or atomic swap fails.

Tests: 10/10 passing.

Implements P4 of the cli-forge-chef brigade plan for #228.
With #286 (P1), #287 (P2), #289 (P3), #228 closes when this lands.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

1 participant