Skip to content

feat(settings): let an MDM profile set a minimum safe mode level - #2214

Merged
datlechin merged 2 commits into
mainfrom
feat/managed-policy
Aug 19, 2026
Merged

feat(settings): let an MDM profile set a minimum safe mode level#2214
datlechin merged 2 commits into
mainfrom
feat/managed-policy

Conversation

@datlechin

Copy link
Copy Markdown
Member

The first piece of managed configuration: an administrator can set a minimum Safe Mode level through a macOS configuration profile, and the user cannot drop below it.

The mechanism is smaller than it sounds, on purpose

macOS already merges a configuration profile's values into UserDefaults at the highest priority, so a forced key simply wins with no special read path. The only thing the ordinary API cannot answer is whether a key was forced, and CFPreferences.h says exactly what that is for:

In cases where machines and/or users are under some kind of management, callers should use this function to determine whether or not to disable UI elements corresponding to those preference keys.

So this adds a flat key namespace and a reader, not a policy engine, an override chain, or a second settings store. ManagedPolicyReader.isManaged exists to disable a control, not to fetch a value.

Why a separate flat key rather than forcing an existing setting

The app's settings are stored as encoded structs under keys like com.TablePro.settings.mcp. Forcing one of those would mean an administrator had to reproduce the entire struct in their profile, and it would break the first time a field was added. Policy keys are one primitive per decision:

Key Type
com.TablePro.policy.minimumSafeModeLevel String
com.TablePro.policy.mcpServerDisabled Bool
com.TablePro.policy.aiAssistantDisabled Bool
com.TablePro.policy.pluginInstallDisabled Bool

Only the first is enforced in this PR. The other three are declared because they are the same shape and the enum is the place a reviewer should see the intended set, but nothing reads them yet, so treat them as reserved rather than shipped.

Two behaviours worth reviewing

The policy is a floor, never a ceiling. A connection set stricter than the policy keeps its own level. Someone who put a production connection on Read-Only still gets Read-Only when the policy only asks for Alert. ManagedPolicyResolverTests checks every ordered pair of the six levels in both directions, so no pair can silently collapse to a tie.

An unrecognised value imposes no floor at all. Not Read-Only, which would lock people out over a typo in a profile, and not Silent, which would drop the policy without saying so.

It is enforced at ExecutionGateProvider's safeModeLevelResolver, which is the single place the gate learns a connection's level, so every caller including the AI assistant and the MCP tools goes through it.

Scope

Safe Mode is a floor on TablePro's behaviour, not on the database. It stops the app issuing a write; it does not stop the same person connecting with psql. The docs say so and point at the existing "Server Read-Only Is Not Safe Mode" section rather than implying more than it does.

No UI change yet. A managed control should render as managed rather than merely preset, and isManaged exists for exactly that, but Settings does not consume it in this PR.

Verification

  • build PASS
  • test ManagedPolicyResolverTests, ManagedPolicyReaderTests: 10 executed, 10 passed
  • lint TablePro 0 violations

Tests use an isolated UserDefaults suite per case, so they need no profile installed and leave no state behind.

@mintlify

mintlify Bot commented Aug 18, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
TablePro 🟢 Ready View Preview Aug 18, 2026, 8:42 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Signed-off-by: Ngô Quốc Đạt <datlechin@gmail.com>
@datlechin
datlechin merged commit 9a05587 into main Aug 19, 2026
8 checks passed
@datlechin
datlechin deleted the feat/managed-policy branch August 19, 2026 02:08
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