Skip to content

Conversation

@gt-oai
Copy link
Contributor

@gt-oai gt-oai commented Dec 9, 2025

Constrain approval_policy through new admin_policy config.

This PR will:

  1. Add a admin_policy section to config, with a single field (for now) allowed_approval_policies. This list constrains the set of user-settable approval_policys.
  2. Introduce a new Constrained<T> type, which combines a current value and a validator function. The validator function ensures disallowed values are not set.
  3. Change the type of approval_policy on Config and SessionConfiguration from AskForApproval to Constrained<AskForApproval>. The validator function is set by the values passed into allowed_approval_policies.
  4. GenericDisplayRow: add a disabled_reason: Option<String>. When set, it disables selection of the value and indicates as such in the menu. This also makes it unselectable with arrow keys or numbers. This is used in the /approvals menu.

Follow ups are:

  1. Do the same thing to sandbox_policy.
  2. Propagate the allowed set of values through app-server for the extension (though already this should prevent app-server from setting this values, it's just that we want to disable UI elements that are unsettable).

Happy to split this PR up if you prefer, into the logical numbered areas above. Especially if there are parts we want to gavel on separately (e.g. admin_policy).

Disabled full access:
image

Disabled --yolo on startup:
image

CODEX-4087

@github-actions
Copy link
Contributor

github-actions bot commented Dec 9, 2025

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@gt-oai
Copy link
Contributor Author

gt-oai commented Dec 9, 2025

I have read the CLA Document and I hereby sign the CLA

github-actions bot added a commit that referenced this pull request Dec 9, 2025
@gt-oai gt-oai force-pushed the admin-policies-allowlist-2 branch 3 times, most recently from b96996e to 697f5c3 Compare December 9, 2025 17:44
@gt-oai gt-oai marked this pull request as ready for review December 9, 2025 17:59
@gt-oai gt-oai force-pushed the admin-policies-allowlist-2 branch from 697f5c3 to 693addf Compare December 9, 2025 18:11
Copy link
Collaborator

@jif-oai jif-oai left a comment

Choose a reason for hiding this comment

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

Maybe good to check with @eb for the UX

impl<T: Send + Sync> Constrained<T> {
pub fn new(
initial_value: T,
validator: impl Fn(&T) -> std::io::Result<()> + Send + Sync + 'static,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Re-use ConstraintValidator in the type def

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tried to do this but didn't seem able too -- something about runtime sizes vs compile-time sizes

@gt-oai gt-oai force-pushed the admin-policies-allowlist-2 branch 3 times, most recently from 1e0cd2d to 30f0162 Compare December 10, 2025 16:12

pub const CONFIG_TOML_FILE: &str = "config.toml";

const ALL_APPROVAL_POLICIES: [AskForApproval; 4] = [
Copy link
Collaborator

Choose a reason for hiding this comment

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

I would move as much of these new types as possible to another/new file. This file is already quite large.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moved into constraint.rs (new) and types.rs.

})
}

pub fn allow_any(initial_value: T) -> Self {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This doesn't help the UI know what the supported values are, though? I guess that is a limitation of ConstraintValidator in general?

Copy link
Contributor Author

@gt-oai gt-oai Dec 11, 2025

Choose a reason for hiding this comment

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

It is a limitation of ConstraintValidator, but we could add this list of possible values to Constrained<T>. I wanted to ensure the abstraction could work when the set of possible values was not enumerable.

self.value
}

pub fn can_set(&self, candidate: &T) -> ConstraintResult<()> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I see: so is the idea we can ask whether a candidate is supported, but we can't get the list of supported candidates? I guess it's more dynamic this way...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I'm happy to extend this (e.g. ConstrainedSet, which directly contains the allowed_values). But I felt for now what we actually wanted is covered by (1) the can_set method and (2) the validator error message.

There's a risk we end up in a hairy ball of config-driven menus which I feel often isn't great for the user.

@gt-oai gt-oai force-pushed the admin-policies-allowlist-2 branch 3 times, most recently from 74c93ed to a5d9bfb Compare December 15, 2025 15:54
Copy link
Collaborator

@jif-oai jif-oai left a comment

Choose a reason for hiding this comment

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

I will defer the @bolinfest to see if this is relevant to covert TUI2 but looks good


impl From<ConstraintError> for std::io::Error {
fn from(err: ConstraintError) -> Self {
std::io::Error::new(std::io::ErrorKind::InvalidInput, err.to_string())
Copy link
Collaborator

Choose a reason for hiding this comment

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

Drop the to_string() to avoid loosing some part of the information

if let Some(idx) = self.state.selected_idx
&& let Some(actual_idx) = self.filtered_indices.get(idx)
&& let Some(item) = self.items.get(*actual_idx)
&& item.disabled_reason.is_none()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Just make sure that skipping disabled, never pushes the selection of screen. Might not be the case but we had the issue before

(next, sandbox_policy_changed)
}
Err(err) => {
let wrapped = ConstraintError {
Copy link
Collaborator

Choose a reason for hiding this comment

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

drop(state) here because we should release the mutex before making an async call (note the .await below) that no longer needs it.

Copy link
Collaborator

@bolinfest bolinfest left a comment

Choose a reason for hiding this comment

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

Just a few small comments, but otherwise LGTM, nice work!

&self,
sub_id: String,
session_configuration: SessionConfiguration,
final_output_json_schema: Option<Option<Value>>,
Copy link
Collaborator

Choose a reason for hiding this comment

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

double Option is a bit of a bad code smell...can this be Option<Value>?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The Option<Option<>> is propagated from SessionSettingsUpdate, and I suppose is used to distinguish between "do nothing" and "explicitly set it to null". I'm not sure if that behaviour is necessary but if it is not I'd prefer the leave the cleanup to a follow up PR.

};

let current_context = sess.new_turn_with_sub_id(sub_id, updates).await;
let Ok(current_context) = sess.new_turn_with_sub_id(sub_id, updates).await else {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should we log an error in the else case?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

new_turn_with_sub_id already emits an error event on failure. I've added a comment to this effect. Can add more logging if that's insufficient.

@gt-oai gt-oai force-pushed the admin-policies-allowlist-2 branch 2 times, most recently from 5e253a2 to 6a261fd Compare December 17, 2025 12:25
@gt-oai gt-oai force-pushed the admin-policies-allowlist-2 branch from 6a261fd to b0025f5 Compare December 17, 2025 15:33
@gt-oai
Copy link
Contributor Author

gt-oai commented Dec 17, 2025

FYI after our discussion, I removed the admin_policy config.toml changes from this PR and will aim to introduce restrictions.toml separately. For now, there's no way to actually set the constraint, and it will instead allow all values.

@gt-oai gt-oai merged commit 9352c6b into main Dec 17, 2025
46 of 48 checks passed
@gt-oai gt-oai deleted the admin-policies-allowlist-2 branch December 17, 2025 16:19
@github-actions github-actions bot locked and limited conversation to collaborators Dec 17, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants