Skip to content

config: enforce enterprise feature requirements#13388

Merged
bolinfest merged 1 commit intomainfrom
pr13388
Mar 4, 2026
Merged

config: enforce enterprise feature requirements#13388
bolinfest merged 1 commit intomainfrom
pr13388

Conversation

@bolinfest
Copy link
Collaborator

@bolinfest bolinfest commented Mar 3, 2026

Why

Enterprises can already constrain approvals, sandboxing, and web search through requirements.toml and MDM, but feature flags were still only configurable as managed defaults. That meant an enterprise could suggest feature values, but it could not actually pin them.

This change closes that gap and makes enterprise feature requirements behave like the other constrained settings. The effective feature set now stays consistent with enterprise requirements during config load, when config writes are validated, and when runtime code mutates feature flags later in the session.

It also tightens the runtime API for managed features. ManagedFeatures now follows the same constraint-oriented shape as Constrained<T> instead of exposing panic-prone mutation helpers, and production code can no longer construct it through an unconstrained From<Features> path.

The PR also hardens the compact_resume_fork integration coverage on Windows. After the feature-management changes, compact_resume_after_second_compaction_preserves_history was overflowing the libtest/Tokio thread stacks on Windows, so the test now uses an explicit larger-stack harness as a pragmatic mitigation. That may not be the ideal root-cause fix, and it merits a parallel investigation into whether part of the async future chain should be boxed to reduce stack pressure instead.

What Changed

Enterprises can now pin feature values in requirements.toml with the requirements-side features table:

[features]
personality = true
unified_exec = false

Only canonical feature keys are allowed in the requirements features table; omitted keys remain unconstrained.

  • Added a requirements-side pinned feature map to ConfigRequirementsToml, threaded it through source-preserving requirements merge and normalization in codex-config, and made the TOML surface use [features] (while still accepting legacy [feature_requirements] for compatibility).
  • Exposed featureRequirements from configRequirements/read, regenerated the JSON/TypeScript schema artifacts, and updated the app-server README.
  • Wrapped the effective feature set in ManagedFeatures, backed by ConstrainedWithSource<Features>, and changed its API to mirror Constrained<T>: can_set(...), set(...) -> ConstraintResult<()>, and result-returning enable / disable / set_enabled helpers.
  • Removed the legacy-usage and bulk-map passthroughs from ManagedFeatures; callers that need those behaviors now mutate a plain Features value and reapply it through set(...), so the constrained wrapper remains the enforcement boundary.
  • Removed the production loophole for constructing unconstrained ManagedFeatures. Non-test code now creates it through the configured feature-loading path, and impl From<Features> for ManagedFeatures is restricted to #[cfg(test)].
  • Rejected legacy feature aliases in enterprise feature requirements, and return a load error when a pinned combination cannot survive dependency normalization.
  • Validated config writes against enterprise feature requirements before persisting changes, including explicit conflicting writes and profile-specific feature states that normalize into invalid combinations.
  • Updated runtime and TUI feature-toggle paths to use the constrained setter API and to persist or apply the effective post-constraint value rather than the requested value.
  • Updated the core_test_support Bazel target to include the bundled core model-catalog fixtures in its runtime data, so helper code that resolves core/models.json through runfiles works in remote Bazel test environments.
  • Renamed the core config test coverage to emphasize that effective feature values are normalized at runtime, while conflicting persisted config writes are rejected.
  • Ran compact_resume_after_second_compaction_preserves_history inside an explicit 8 MiB test thread and Tokio runtime worker stack, following the existing larger-stack integration-test pattern, to keep the Windows compact_resume_fork test slice from aborting while a parallel investigation continues into whether some of the underlying async futures should be boxed.

Verification

  • cargo test -p codex-config
  • cargo test -p codex-core feature_requirements_ -- --nocapture
  • cargo test -p codex-core load_requirements_toml_produces_expected_constraints -- --nocapture
  • cargo test -p codex-core compact_resume_after_second_compaction_preserves_history -- --nocapture
  • cargo test -p codex-core compact_resume_fork -- --nocapture
  • Re-ran the built codex-core tests/all binary with RUST_MIN_STACK=262144 for compact_resume_after_second_compaction_preserves_history to confirm the explicit-stack harness fixes the deterministic low-stack repro.
  • cargo test -p codex-core
    • This still fails locally in unrelated integration areas that expect the codex / test_stdio_server binaries or hit existing search_tool wiremock mismatches.

Docs

developers.openai.com/codex should document the requirements-side [features] table for enterprise and MDM-managed configuration, including that it only accepts canonical feature keys and that conflicting config writes are rejected.

Copy link
Contributor

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

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: 5e667824d0

ℹ️ 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 +1826 to +1829
let features = apply_feature_requirements(
configured_features,
requirements.feature_requirements.clone(),
)?;
Copy link
Contributor

Choose a reason for hiding this comment

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

P1 Badge Re-apply feature requirements after runtime flag updates

feature_requirements are only enforced once during Config construction here, but the running app can later mutate config.features directly (for example via AppEvent::UpdateFeatureFlags in tui/src/app.rs, which calls enable/disable without checking requirements). In a managed environment this allows users to re-enable features that were pinned to false by enterprise requirements after startup, so the new policy is bypassable for the active session.

Useful? React with 👍 / 👎.

@bolinfest bolinfest changed the title config: support enterprise feature requirements config: enforce enterprise feature requirements Mar 3, 2026
@bolinfest bolinfest force-pushed the pr13388 branch 4 times, most recently from 08aa186 to 44b4b91 Compare March 4, 2026 00:14
@bolinfest bolinfest force-pushed the pr13388 branch 6 times, most recently from 5842091 to db5ef09 Compare March 4, 2026 02:27
}

#[tokio::test]
async fn feature_requirements_override_default_feature_values() -> std::io::Result<()> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I’m a bit confused. I see that we reject/raise errors during validation, but this test is about overriding. What is the intended behavior in this PR — when to reject vs override?

Personally, I think override should be the general approach if possible.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The intended behavior is override for effective runtime state, but reject for explicit config writes. We normalize the loaded/in-memory feature set so enterprise requirements always win, but we reject persisted conflicting writes so we don’t save misleading config that can never take effect.

@bolinfest bolinfest enabled auto-merge (squash) March 4, 2026 03:28
@bolinfest bolinfest disabled auto-merge March 4, 2026 04:11
@bolinfest bolinfest enabled auto-merge (squash) March 4, 2026 04:18
@bolinfest bolinfest merged commit bfff0c7 into main Mar 4, 2026
99 of 109 checks passed
@bolinfest bolinfest deleted the pr13388 branch March 4, 2026 04:40
@github-actions github-actions bot locked and limited conversation to collaborators Mar 4, 2026
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