policy: resolve managed settings per-key across delivery channels#323780
Merged
Conversation
Managed settings previously used a single authoritative source: the first non-empty delivery channel (native MDM > server > file) won wholesale and the others were ignored entirely. Switch to per-key precedence: the same order is honored, but resolved key-by-key. A key locked by a higher-precedence channel still cannot be overwritten, while keys a higher channel leaves unset are now filled in by a lower channel. Centralize the resolution in a new pickManagedSettings() (replacing selectManagedSettings) that returns the merged bag, per-key provenance, and the active sources, so policy evaluation and the Policy Diagnostics report share one implementation. Build the merged bag with Object.fromEntries so an untrusted __proto__ key cannot corrupt its prototype chain. Rework the Policy Diagnostics report to show every contributing source, a per-key Resolution table (effective value, winning source, and struck-through overrides), and per-key policy attribution. Add unit coverage for the per-key merge (provenance, fill-down, falsy values, ordering, prototype-pollution guard) plus an end-to-end test proving two keys can win from two different channels and both reach policy evaluation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This pull request updates VS Code’s Copilot managed-settings handling so that precedence is resolved per key across delivery channels (native MDM → server → file), and upgrades Policy Diagnostics to explain per-key provenance and effective values.
Changes:
- Replaces the “single authoritative bag” selection logic with
pickManagedSettings()that merges managed settings key-by-key and records per-key resolutions + active sources. - Updates
AccountPolicyServiceand Developer: Policy Diagnostics to consume the new per-key resolution output and report per-key attribution. - Adds new unit tests for
pickManagedSettings()plus an end-to-end test validating that different keys can win from different channels.
Show a summary per file
| File | Description |
|---|---|
| src/vs/workbench/services/policies/test/browser/accountPolicyService.test.ts | Adds an end-to-end test validating per-key fill-down across native MDM + file managed settings. |
| src/vs/workbench/services/policies/common/accountPolicyService.ts | Switches policy evaluation from whole-bag selection to per-key resolution (pickManagedSettings). |
| src/vs/workbench/browser/actions/developerActions.ts | Reworks Policy Diagnostics to display plural active sources and a per-key resolution table + attribution. |
| src/vs/platform/policy/test/common/copilotManagedSettings.test.ts | Adds a dedicated test suite for per-key resolution behavior and provenance. |
| src/vs/platform/policy/common/copilotManagedSettings.ts | Introduces ManagedSettingsChannel, MANAGED_SETTINGS_CHANNELS, resolution/provenance types, and pickManagedSettings(). |
| .github/skills/add-policy/SKILL.md | Updates policy skill documentation to reflect per-key managed-settings precedence. |
| .github/skills/add-policy/github-managed-settings.md | Updates managed-settings documentation to reflect the new per-key merge/precedence model. |
Review details
- Files reviewed: 7/7 changed files
- Comments generated: 1
- Review effort level: Low
Address PR feedback: switch pickManagedSettings from `for...in` to a guarded `Object.keys` loop so only own enumerable properties are visited (managed-settings bags are untrusted input) and absent channels are skipped explicitly. Note: `for...in` over an undefined bag was already a no-op (never threw), so this is a robustness/clarity improvement rather than a crash fix. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
joshspicer
marked this pull request as ready for review
July 1, 2026 20:22
Drop two redundant cases (distinct-keys and standalone activeSources ordering, both already covered by the headline and empty/absent tests) and fold the non-contributing-middle-channel ordering check into the empty/absent snapshot. Verified against the live node unit runner (all green). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
DonJayamanne
approved these changes
Jul 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Managed settings previously used a single authoritative source: the first non-empty delivery channel (native MDM > server > file) won wholesale and the others were ignored entirely. This switches to per-key precedence — the same order is honored, but resolved key-by-key:
How
selectManagedSettingswithpickManagedSettings()incopilotManagedSettings.ts. It walksMANAGED_SETTINGS_CHANNELShighest-first and returns:values— the effective merged bag,resolutions— per-key provenance (winning source + every channel's contribution, winner first),activeSources— channels that won at least one key, in precedence order.Object.fromEntriesso an untrusted__proto__key can't corrupt its prototype chain.AccountPolicyService.getPolicyDataconsumes the picker;activeSources.length === 0replaces the oldsource === 'none'short-circuit.Tests
pickManagedSettingsunit suite: per-key resolution + provenance, mid-tier (server) winning when native is absent, distinct keys winning from distinct channels, falsy-but-present values winning,undefined-hole fall-through, precedence-orderedactiveSources, fresh-object guarantee, empty/all-empty, and a prototype-pollution guard.accountPolicyService.test.ts: native MDM wins one key and the file wins another — both reach policy evaluation (per-key fill-down across the full stack).Notes for reviewers
enabledPluginsdeny-wins) are unchanged.values[key] = …) invoked the__proto__setter on the returned bag;Object.fromEntries(define-property semantics) does not. The global prototype was never at risk and downstream reads only declared keys, so no functional bug shipped..github/skills/add-policy/SKILL.mdandgithub-managed-settings.md.tsgotypecheck clean. The repo's gulp hygiene hook and the live Mocha suite couldn't run in this worktree (missing build deps / noout/build); the per-key algorithm was additionally verified against a faithful functional port.Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com