From 3be0e61235d5b3c12eef71144a89648cfd2d04c5 Mon Sep 17 00:00:00 2001 From: Josh Spicer <23246594+joshspicer@users.noreply.github.com> Date: Thu, 6 Aug 2026 15:41:04 -0700 Subject: [PATCH 1/3] docs: simplify enterprise policy guidance Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/add-policy/SKILL.md | 364 +++--------------- .github/skills/add-policy/extension-policy.md | 46 +++ .../add-policy/github-managed-settings.md | 34 +- .../add-policy/legacy-permission-policy.md | 29 ++ .github/skills/add-policy/mixed-policy.md | 28 ++ .../skills/add-policy/sdk-runtime-policy.md | 71 ++++ .github/skills/add-policy/vscode-policy.md | 96 +++++ 7 files changed, 335 insertions(+), 333 deletions(-) create mode 100644 .github/skills/add-policy/extension-policy.md create mode 100644 .github/skills/add-policy/legacy-permission-policy.md create mode 100644 .github/skills/add-policy/mixed-policy.md create mode 100644 .github/skills/add-policy/sdk-runtime-policy.md create mode 100644 .github/skills/add-policy/vscode-policy.md diff --git a/.github/skills/add-policy/SKILL.md b/.github/skills/add-policy/SKILL.md index cd5c4dc8f5077..97c7762d4ed9e 100644 --- a/.github/skills/add-policy/SKILL.md +++ b/.github/skills/add-policy/SKILL.md @@ -1,341 +1,63 @@ --- name: add-policy -description: Use when adding, modifying, or reviewing VS Code configuration policies. Covers the full policy lifecycle from registration to export to platform-specific artifacts. Run on ANY change that adds a `policy:` field to a configuration property. +description: Use when adding, modifying, or reviewing enterprise controls for VS Code or the GitHub Copilot SDK/runtime. Start here to decide whether policy belongs in runtime managed settings, VS Code configuration policy, extension policy, or a split implementation. Run on ANY change that adds a `policy:` field. --- -# Adding a Configuration Policy +# Adding an Enterprise Policy -Policies allow enterprise administrators to lock configuration settings via OS-level mechanisms (Windows Group Policy, macOS managed preferences, Linux config files) or via Copilot account-level policy data. This skill covers the complete procedure. +Choose the policy destination by **where the governed behavior is implemented**, not by +which team requested it. Most controls for Copilot agent behavior belong in the +SDK/runtime rather than VS Code. -## When to Use +```mermaid +flowchart TD + A[Enterprise control] --> P{Existing permission policy
introduced before VS Code 1.133.0?} + P -->|Yes| L[Pre-1.133 compatibility migration] + P -->|No| B{Where is the governed behavior implemented?} -- Adding a new `policy:` field to any configuration property -- Modifying an existing policy (rename, category change, etc.) -- Reviewing a PR that touches policy registration -- Adding account-based policy support via `IPolicyData` -- Wiring an enterprise **managed setting** (native MDM / GitHub server) — see **[github-managed-settings.md](./github-managed-settings.md)** -- Having one policy govern **multiple** settings via `policyReference` -- **Testing** account/managed-settings policies locally without the real backend — see **[local-testing.md](./local-testing.md)** - -## Architecture Overview - -### Policy Sources (layered, last writer wins) - -| Source | Implementation | How it reads policies | -|--------|---------------|----------------------| -| **OS-level** (Windows registry, macOS plist) | `NativePolicyService` via `@vscode/policy-watcher` | Watches `Software\Policies\Microsoft\{productName}` (Windows) or bundle identifier prefs (macOS) | -| **Linux file** | `FilePolicyService` | Reads `/etc/vscode/policy.json` | -| **Account/GitHub** | `AccountPolicyService` | Reads `IPolicyData` from `IDefaultAccountService.policyData`, applies `value()` function. Server-delivered managed settings arrive on `policyData.managedSettings`; native MDM (`INativeManagedSettingsService`) and a file on disk (`IFileManagedSettingsService`) are **separate** inputs that `AccountPolicyService` merges in `getPolicyData()` via `pickManagedSettings(nativeMdm, server, file)` (per-key precedence native MDM > server > file; a key locked by a higher channel cannot be overwritten, keys it leaves unset fall through to lower channels) | -| **Copilot managed settings (native MDM)** | `NativeManagedSettingsService` via `@vscode/policy-watcher` | Watches `SOFTWARE\Policies\GitHubCopilot` (Windows) / `com.github.copilot` prefs (macOS); feeds the canonical `managedSettings` bag — see [github-managed-settings.md](./github-managed-settings.md) | -| **Copilot managed settings (file)** | `FileManagedSettingsService` | Reads + watches `managed-settings.json` from a well-known per-OS path in the main process, exposed to renderers over IPC; lowest-precedence managed-settings channel — see [github-managed-settings.md](./github-managed-settings.md) | -| **Multiplex** | `MultiplexPolicyService` | In the main process, combines multiple OS/file policy readers; in desktop and Agents-window renderers, combines the main-process `PolicyChannelClient` with `AccountPolicyService` | - -### Key Files - -| File | Purpose | -|------|---------| -| `src/vs/base/common/policy.ts` | `PolicyCategory` enum, `IPolicy` interface, `IPolicyReference`, `ManagedSettingsData`, `IManagedSettingsPolicyDefinitions` | -| `src/vs/platform/policy/common/policy.ts` | `IPolicyService`, `AbstractPolicyService`, `PolicyDefinition`, `toSerializablePolicyDefinition` (drops the non-cloneable `value()` for IPC), `getRestrictedPolicyValue` | -| `src/vs/platform/policy/common/copilotManagedSettings.ts` | Managed-settings key constants + well-known file paths, `collectManagedSettingsDefinitions`, `projectManagedSettings`, the shared `normalizeManagedSettings` (single normalizer for all channels), `pickManagedSettings` (per-key channel precedence), `INativeManagedSettingsService` / `IFileManagedSettingsService` | -| `src/vs/platform/policy/node/nativeManagedSettingsService.ts` | Native MDM watcher (`@vscode/policy-watcher`) for Copilot managed settings | -| `src/vs/platform/policy/common/fileManagedSettingsService.ts` | File-based channel: reads + watches `managed-settings.json` on a well-known per-OS path, normalizes via `normalizeManagedSettings` | -| `src/vs/platform/configuration/common/configurations.ts` | `PolicyConfiguration` — bridges policies to configuration values; parses JSON-string managed settings back to typed values; applies values to `policyReference` settings | -| `src/vs/platform/configuration/common/configurationRegistry.ts` | `policy` / `policyReference` registration; `getPolicyReferenceConfigurations()` (name → subordinate settings) | -| `src/vs/workbench/services/policies/common/accountPolicyService.ts` | Account/GitHub-based policy evaluation; selects + projects managed settings (native MDM over server; single authoritative layer) | -| `src/vs/workbench/services/accounts/browser/managedSettings.ts` | `adaptManagedSettings` — normalizes the server `managed_settings` response into the canonical bag | -| `src/vs/workbench/services/policies/common/multiplexPolicyService.ts` | Combines multiple policy services | -| `src/vs/workbench/contrib/policyExport/electron-browser/policyExport.contribution.ts` | `--export-policy-data` CLI handler | -| `src/vs/base/common/defaultAccount.ts` | `IPolicyData` interface (incl. `managedSettings`) for account-level policy fields | -| `build/lib/policies/policyData.jsonc` | Auto-generated policy catalog incl. `referencedSettings` (DO NOT edit manually) | -| `build/lib/policies/policyGenerator.ts` | Generates ADMX/ADML (Windows), plist (macOS), JSON (Linux) | -| `build/lib/test/policyConversion.test.ts` | Tests for policy artifact generation | - -## Procedure - -### Step 1 — Add the `policy` field to the configuration property - -Find the configuration registration (typically in a `*.contribution.ts` file) and add a `policy` object to the property schema. - -**Required fields:** - -**Determining `minimumVersion`:** Always read `version` from the root `package.json` and use the `major.minor` portion. For example, if `package.json` has `"version": "1.112.0"`, use `minimumVersion: '1.112'`. Never hardcode an old version like `'1.99'`. - -```typescript -policy: { - name: 'MyPolicyName', // PascalCase, unique across all policies - category: PolicyCategory.InteractiveSession, // From PolicyCategory enum - minimumVersion: '1.112', // Use major.minor from package.json version - localization: { - description: { - key: 'my.config.key', // NLS key for the description - value: nls.localize('my.config.key', "Human-readable description."), - } - } -} -``` - -**Optional: `value` function for account-based policy:** - -If this policy should also be controllable via Copilot account policy data (from `IPolicyData`), add a `value` function: - -```typescript -policy: { - name: 'MyPolicyName', - category: PolicyCategory.InteractiveSession, - minimumVersion: '1.112', // Use major.minor from package.json version - value: (policyData) => policyData.my_field === false ? false : undefined, - localization: { /* ... */ } -} -``` - -The `value` function receives `IPolicyData` (from `src/vs/base/common/defaultAccount.ts`) and should: -- Return a concrete value to **override** the user's setting -- Return `undefined` to **not apply** any account-level override (falls through to OS policy or user setting) - -If you need a new field on `IPolicyData`, add it to the interface in `src/vs/base/common/defaultAccount.ts`. - -**Optional: `enumDescriptions` for enum/string policies:** - -**IMPORTANT:** If the configuration property has `type: 'string'` and an `enum` array, you **must** include `enumDescriptions` in the `localization` block with the same number of entries as the `enum` array. Without this, `npm run export-policy-data` will fail with: `enumDescriptions must exist and have the same length as enum for policy "..."`. - -```typescript -localization: { - description: { key: '...', value: nls.localize('...', "...") }, - enumDescriptions: [ - { key: 'opt.none', value: nls.localize('opt.none', "No access.") }, - { key: 'opt.all', value: nls.localize('opt.all', "Full access.") }, - ] -} -``` - -### Step 2 — Ensure `PolicyCategory` is imported - -```typescript -import { PolicyCategory } from '../../../../base/common/policy.js'; + B -->|Copilot runtime, tools, MCP,
sandbox, or agent loop| R[SDK/runtime managed setting] + B -->|VS Code editor or workbench| V[VS Code configuration policy] + B -->|Extension-provided setting| E[Extension policy] + B -->|Independent runtime and editor behavior| M[Split runtime/editor control] ``` -Existing categories in the `PolicyCategory` enum: -- `Extensions` -- `IntegratedTerminal` -- `InteractiveSession` (used for all chat/Copilot policies) -- `Telemetry` -- `Update` - -If you need a new category, add it to `PolicyCategory` in `src/vs/base/common/policy.ts` and add corresponding `PolicyCategoryData` localization. - -### Step 3 — Validate TypeScript compilation - -Check the `VS Code - Build` watch task output, or run: - -```bash -npm run typecheck-client -``` - -### Step 4 — Export the policy data - -Regenerate the auto-generated policy catalog: - -```bash -npm run export-policy-data -``` - -This script handles transpilation, sets up `GITHUB_TOKEN` (via `gh` CLI or GitHub OAuth device flow), and runs `--export-policy-data`. The export command reads extension configuration policies from the distro's `product.json` via the GitHub API and merges them into the output. - -The command launches both the regular Workbench and the Agents window with isolated, empty -profiles, then merges their policy catalogs into one deterministic union. It fails if both -entrypoints register conflicting metadata for the same policy. This ensures policies and -`policyReference` settings from either product surface are included. - -This updates `build/lib/policies/policyData.jsonc`. **Never edit or synthesize this file -manually, including with an LLM. Do not invoke `--export-policy-data` directly.** The npm -command is the only supported generator because a single product entrypoint produces an -incomplete catalog. Verify your policy appears in the output and include the generated file -in the same change. You will need code review from a codeowner to merge the change to main. - - -## Policy for extension-provided settings +Follow the matching guide: -Extension authors cannot add `policy:` fields directly—their settings are defined in the extension's `package.json`, not in VS Code core. Instead, policies for extension settings are defined in `vscode-distro`'s `product.json` under the `extensionConfigurationPolicy` key. +- [SDK/runtime managed setting](./sdk-runtime-policy.md) +- [VS Code configuration policy](./vscode-policy.md) +- [Extension-provided setting](./extension-policy.md) +- [Split runtime/editor control](./mixed-policy.md) +- [Pre-1.133 permission-policy migration](./legacy-permission-policy.md) -### How it works +General rules: -1. **Source of truth**: The `extensionConfigurationPolicy` map lives in `vscode-distro` under `mixin/{quality}/product.json` (stable, insider, exploration). -2. **Runtime**: When VS Code starts with a distro-mixed `product.json`, `configurationExtensionPoint.ts` reads `extensionConfigurationPolicy` and attaches matching `policy` objects to extension-contributed configuration properties. -3. **Export/build**: The `--export-policy-data` command fetches the distro's `product.json` at the commit pinned in `package.json` and merges extension policies into the output. Use `npm run export-policy-data` which sets up authentication automatically. - -### Distro format - -Each entry in `extensionConfigurationPolicy` must include: - -```json -"extensionConfigurationPolicy": { - "publisher.extension.settingName": { - "name": "PolicyName", - "category": "InteractiveSession", - "minimumVersion": "1.99", - "description": "Human-readable description." - } -} -``` - -- `name`: PascalCase policy name, unique across all policies -- `category`: Must be a valid `PolicyCategory` enum value (e.g., `InteractiveSession`, `Extensions`) -- `minimumVersion`: The VS Code version that first shipped this policy -- `description`: Human-readable description string used to generate localization key/value pairs for ADMX/ADML/macOS/Linux policy artifacts - -### Adding a new extension policy - -1. Add the entry to `extensionConfigurationPolicy` in **all three** quality `product.json` files in `vscode-distro` (`mixin/stable/`, `mixin/insider/`, `mixin/exploration/`) -2. Update the `distro` commit hash in `package.json` to point to the distro commit that includes your new entry — the export command fetches extension policies from the pinned distro commit -3. Regenerate `policyData.jsonc` by running `npm run export-policy-data` (see Step 4 above) -4. Update the test fixture at `src/vs/workbench/contrib/policyExport/test/node/extensionPolicyFixture.json` with the new entry - -### Test fixtures - -The file `src/vs/workbench/contrib/policyExport/test/node/extensionPolicyFixture.json` is a test fixture that must stay in sync with the extension policies in the checked-in `policyData.jsonc`. When extension policies are added or changed in the distro, this fixture must be updated to match — otherwise the integration test will fail because the test output (generated from the fixture) won't match the checked-in file (generated from the real distro). - -### Downstream consumers - -| Consumer | What it reads | Output | -|----------|--------------|--------| -| `policyGenerator.ts` | `policyData.jsonc` | ADMX/ADML (Windows GP), `.mobileconfig` (macOS), `policy.json` (Linux) | -| `vscode-website` (`gulpfile.policies.js`) | `policyData.jsonc` | Enterprise policy reference table at code.visualstudio.com/docs/enterprise/policies | -| `vscode-docs` | Generated from website build | `docs/enterprise/policies.md` | - -## GitHub Preview Features - -If your setting is a **GitHub Preview Feature** — meaning it's a Copilot/chat feature that organizations can disable via their GitHub account-level policy — you **must** add a `value` function that checks `policyData.chat_preview_features_enabled`. - -### When to add this flag - -Add the `chat_preview_features_enabled` check when **all** of these apply: - -- The setting controls a Copilot or chat feature (e.g., agent tools, hooks, MCP, auto-approve) -- The feature is in preview or experimental status (typically tagged `'preview'` or `'experimental'`) -- An organization admin should be able to disable it for all users in their org via GitHub account policy - -### How it works - -The `chat_preview_features_enabled` field on `IPolicyData` (defined in `src/vs/base/common/defaultAccount.ts`) is populated from the user's GitHub Copilot token entitlements. When an organization admin disables preview features, `chat_preview_features_enabled` is set to `false`. - -### Pattern - -Add a `value` function to the policy that returns a disabling value when `chat_preview_features_enabled === false`, and `undefined` otherwise (to fall through to the user's own setting): - -```typescript -policy: { - name: 'MyPreviewFeaturePolicy', - category: PolicyCategory.InteractiveSession, - minimumVersion: '1.xx', // Must match the first VS Code release that ships this policy. - value: (policyData) => policyData.chat_preview_features_enabled === false ? false : undefined, - localization: { - description: { - key: 'my.setting.description', - value: nls.localize('my.setting.description', "Description of the setting."), - } - } -} -``` - -Key details: -- **Always compare with `=== false`**, not `!policyData.chat_preview_features_enabled` — the field is optional and `undefined` means "no policy data available", which should not disable the feature. -- **Return `undefined`** when the flag is not `false` so the account-level policy does not override the user's setting. -- **Return the disabling value** for the setting's type: `false` for booleans, a restrictive string/enum value for other types. - -### Real-world examples - -See `chat.tools.global.autoApprove` and `chat.useHooks` in `src/vs/workbench/contrib/chat/browser/chat.shared.contribution.ts` for existing settings that use this pattern. - -## Enterprise Managed Settings (native MDM / GitHub server) - -GitHub Copilot enterprise admins can lock settings through a **managed-settings** bag. -VS Code feeds the bag from **two** channels: native MDM (Windows registry / macOS plist) -and the GitHub `/copilot_internal/managed_settings` endpoint. (The external -`managed-settings-schema.json` also describes a `managed-settings.json` file channel, but -VS Code does **not** read such a file.) Both VS Code channels converge on -`IPolicyData.managedSettings` (a flat dot-path bag) and are consumed by the **existing** -`policy.value(policyData)` callback — there is no new `IPolicyService`. - -To drive a policy from a managed setting, declare `managedSettings` on the policy and -read `policyData.managedSettings?.[KEY]` in `value` (the real `ChatToolsAutoApprove` also -ORs in `chat_preview_features_enabled === false`): - -```typescript -// Existing policy shown verbatim; `minimumVersion: '1.99'` is its historical value — -// a NEW policy derives minimumVersion from package.json major.minor (see Step 1). -policy: { - name: 'ChatToolsAutoApprove', - category: PolicyCategory.InteractiveSession, - minimumVersion: '1.99', - value: (policyData) => - policyData.managedSettings?.[COPILOT_DISABLE_BYPASS_PERMISSIONS_MODE_KEY] === 'disable' - || policyData.chat_preview_features_enabled === false ? false : undefined, - managedSettings: { - [COPILOT_DISABLE_BYPASS_PERMISSIONS_MODE_KEY]: { type: 'string' }, - }, - localization: { /* ... */ } -} -``` - -**This is its own modality — full details, schema source of truth, helpers, wiring, and -the new-key checklist are in [github-managed-settings.md](./github-managed-settings.md).** -Read it before adding or reviewing any managed-settings key. - -**Testing locally:** to exercise the account/managed-settings flow without the real -GitHub backend, use the mock policy server — see -**[local-testing.md](./local-testing.md)**. - -## One Policy for Many Settings (`policyReference`) - -A single policy can govern multiple settings (e.g. gate an agent in both the editor -window and the Agents window). The **owner** declares the full `policy: { name, … }`; -other settings declare `policyReference: { name }` pointing at the owner's policy name. - -```typescript -// Owner setting (existing policy; `minimumVersion: '1.126'` is its historical value — -// a NEW policy uses package.json major.minor, see Step 1) -policy: { name: 'Codex3PIntegration', category: PolicyCategory.InteractiveSession, minimumVersion: '1.126', /* ... */ } - -// Subordinate setting (no type/value/localization of its own) -policyReference: { name: 'Codex3PIntegration' } -``` +- Runtime enforcement is authoritative for behavior executed inside the runtime. +- Do not duplicate a runtime parser, matcher, or security decision in VS Code. +- A VS Code policy is appropriate only for editor/workbench-owned behavior. +- New Copilot enterprise controls should target the shared managed-settings/SDK model. +- Run `npm run export-policy-data` for every VS Code or extension policy change. Never + edit `build/lib/policies/policyData.jsonc` manually. -`policyReference` is not managed-settings-specific: use it whenever one enterprise -policy should lock multiple settings to the same value. The reference is a **pure -pointer**. It contributes no type, `value`, `managedSettings`, `restrictedValue`, or -localization of its own; the owner remains the single source of truth for policy -metadata and runtime behavior. +## Deprecated and Historical Channels -Key rules and internals: +Some policy channels remain supported for existing controls but are closed to new +properties: -- A setting must **not** declare both `policy` and `policyReference` (rejected during - configuration registration). -- Exactly one setting may own a policy name with `policy`; additional settings attach - with `policyReference`. -- The reference setting's type must match the owner's type; `npm run export-policy-data` - enforces this because the same resolved policy value is applied verbatim to owner and - references. -- `ConfigurationRegistry.getPolicyReferenceConfigurations()` tracks `policyName → - Set`, and `PolicyConfiguration` updates both the owner setting and all - registered references when the policy value changes. -- `AbstractPolicyService.serialize()` uses `toSerializablePolicyDefinition()` to strip - the non-cloneable `value()` callback before sending policy definitions over IPC. -- `AbstractPolicyService.updatePolicyDefinitions()` replaces definitions per policy - name, so a late-registering owner supersedes an earlier reference fallback; if the - owner is removed, a reference can still provide a bare type fallback. -- Exported policy data includes `referencedSettings` for references that are registered - during export, and **Developer: Policy Diagnostics** lists registered owner/reference - settings under the same policy name. +- **GitHub token/account policy data** (`IPolicyData` fields consumed by + `AccountPolicyService`) is deprecated for new controls. Do not add new entitlement or + policy properties from the GitHub token. Existing fields remain for compatibility. +- New Copilot enterprise controls use managed settings and runtime/SDK enforcement. +- Pre-1.133 permission-policy translation is a bounded migration, not a reusable channel. -For managed-settings-specific examples that combine `policyReference` with Copilot -managed settings, see [github-managed-settings.md](./github-managed-settings.md). +When another channel is deprecated, record the boundary here and keep implementation +details in the relevant destination guide. -## Examples +Supporting references: -Search the codebase for `policy:` to find all the examples of different policy configurations. +- [GitHub Copilot managed settings](./github-managed-settings.md) +- [Local policy testing](./local-testing.md) -## Learnings +Keep these guides contract-focused. Document contributor decisions and behavioral +invariants; point to source rather than copying implementation that will drift. -* Never hand-edit or LLM-generate `build/lib/policies/policyData.jsonc` (its header explicitly forbids it). If `npm run export-policy-data` fails, fix the command or its source policies instead of patching the JSON. Always run it from the worktree containing the policy source change. -* A direct `scripts/code.{sh,bat} --export-policy-data` invocation exports only the selected product entrypoint and is necessarily incomplete. Always use `npm run export-policy-data`, which isolates profiles, exports both the Workbench and Agents window, and merges their catalogs. The blocking `PolicyExport` integration test runs this same canonical path in check mode. -* Document **behavior and business-logic expectations**, not copy-pasted implementation. Reproducing internal code (e.g. the `getPolicyData()` merge body) in the skill rots the moment the source changes and adds no information beyond the source itself. State the contract in prose (e.g. "native MDM managed settings win over the server-delivered channel; the two layers are never merged") and point to the source for the implementation. Reserve code blocks for the **author-facing API contract** a contributor must follow — how to *declare* a `policy` / `managedSettings` / `value` callback — not for restating runtime plumbing. +Trust executable source and tests over planning documents. diff --git a/.github/skills/add-policy/extension-policy.md b/.github/skills/add-policy/extension-policy.md new file mode 100644 index 0000000000000..c152a1bab69d7 --- /dev/null +++ b/.github/skills/add-policy/extension-policy.md @@ -0,0 +1,46 @@ +# Extension-Provided Setting Policy + +Use this path when the setting is contributed by an extension rather than VS Code core. +Extensions cannot declare core `policy:` metadata in their `package.json`. + +The source of truth is `extensionConfigurationPolicy` in `vscode-distro` +`mixin/{quality}/product.json`. At runtime, VS Code attaches that metadata to matching +extension configuration properties. + +```json +"publisher.extension.settingName": { + "name": "PolicyName", + "category": "InteractiveSession", + "minimumVersion": "1.133", + "description": "Human-readable description." +} +``` + +## Procedure + +1. Add the setting to `extensionConfigurationPolicy` in every `vscode-distro` + product-quality `product.json` (`stable`, `insider`, and `exploration`). +2. Include: + - unique PascalCase `name`; + - valid `PolicyCategory`; + - first shipping `minimumVersion`; + - human-readable `description`. +3. Update VS Code's pinned distro commit. +4. Update + `src/vs/workbench/contrib/policyExport/test/node/extensionPolicyFixture.json`. +5. Run `npm run export-policy-data`. +6. Verify and include `build/lib/policies/policyData.jsonc`. + +The fixture must match the pinned distro policy data or the policy-export integration +test fails. + +The canonical export launches both Workbench and the Agents window with isolated +profiles and merges their policy catalogs. Do not invoke a single entrypoint directly. + +Downstream consumers: + +| Consumer | Output | +|---|---| +| `build/lib/policies/policyGenerator.ts` | Windows ADMX/ADML, macOS mobileconfig, Linux policy JSON | +| `vscode-website` policy build | Enterprise policy reference | +| `vscode-docs` | Generated enterprise policy documentation | diff --git a/.github/skills/add-policy/github-managed-settings.md b/.github/skills/add-policy/github-managed-settings.md index c8b54591501d4..b61ded7183a50 100644 --- a/.github/skills/add-policy/github-managed-settings.md +++ b/.github/skills/add-policy/github-managed-settings.md @@ -68,10 +68,14 @@ Client-side merging still happens *within* a channel's value (e.g. `enabledPlugi When the developer has `copilot-agent-runtime` checked out side-by-side, reference `copilot-agent-runtime/schema/managed-settings-schema.json` as the authoritative shape. It is aligned with the `managed_settings` API output and is the schema for all -delivery channels (MDM plist/registry, file-based, server-managed). Its top-level -properties today are `permissions`, `enabledPlugins`, `extraKnownMarketplaces`, and -`strictKnownMarketplaces` (nested objects/arrays). Note the schema is **nested**, whereas -the VS Code bag is **flattened** to dot-paths — e.g. the schema's nested +delivery channels (MDM plist/registry, file-based, server-managed). The runtime schema +also contains keys that VS Code never projects because their behavior is runtime-owned. +The table below contains selected VS Code-projected examples with non-obvious composition +or encoding. It is not an inventory; derive the current key set from policy declarations +and the runtime schema. + +The schema is **nested**, whereas the VS Code bag is **flattened** to dot-paths — e.g. +the schema's nested `permissions.disableBypassPermissionsMode` becomes the flat bag key of the same name (the `COPILOT_DISABLE_BYPASS_PERMISSIONS_MODE_KEY` constant): @@ -148,8 +152,9 @@ Key rules for the `value` callback: fields were **removed**; everything is the canonical bag now). - Return the **locking value** when the managed setting demands it, `undefined` otherwise (so the user's setting falls through). -- It's fine to combine with `chat_preview_features_enabled === false` (see SKILL.md's - "GitHub Preview Features" section). +- Existing policies may also contain the historical + `chat_preview_features_enabled === false` token-entitlement check. Preserve it when + maintaining those policies, but do not add it to a new control. For the **common pass-through case** — lock to the managed value, otherwise fall through to the user's setting — use the `managedSettingValue(KEY)` helper instead of hand-writing the @@ -161,8 +166,8 @@ import { managedSettingValue } from '../../../../platform/policy/common/copilotM value: managedSettingValue(COPILOT_ENABLED_PLUGINS_KEY), ``` -Policies that combine the managed value with other conditions (like `ChatToolsAutoApprove` -above, which also checks `chat_preview_features_enabled`) keep a custom callback. The helper +Existing policies that combine the managed value with historical conditions (like +`ChatToolsAutoApprove` above) keep a custom callback. The helper returns a callback memoized per key (the same function reference for a given key on every call), preserving the policy-definition reference identity that lets `isSamePolicyDefinition` avoid needless re-registration. @@ -294,7 +299,11 @@ API, which returns the same payload as `session.managed_settings_resolved` witho active session. This runtime snapshot is not treated as another VS Code delivery channel because the runtime owns its schema and authority resolution independently. -## Adding a brand-new managed-settings key (checklist) +## Projecting a managed-settings key into VS Code (checklist) + +Follow this checklist only after the root [SKILL.md](./SKILL.md) routes the control to a +VS Code or split runtime/editor policy. Runtime-only managed settings need no VS Code +constant, configuration policy, or policy-data export. 1. **Pick the canonical dot-path** and add it as a constant in `copilotManagedSettings.ts`. It must match the server `managed_settings` API field / @@ -315,9 +324,10 @@ the runtime owns its schema and authority resolution independently. `managed-settings-schema.json` must agree on the key name and value type. The declaration-driven projection (`projectManagedSettings`) silently drops anything that doesn't match the declared type, so a type drift = a silently ignored setting. -5. **Export & test** as in SKILL.md Step 3–4 (`npm run typecheck-client`, - `npm run export-policy-data`). Verify the policy appears in `policyData.jsonc`. A transport-only - control has no policy catalog entry, so it does not require a policy-data export. +5. **Export & test** as described in [vscode-policy.md](./vscode-policy.md) + (`npm run typecheck-client` when needed, then `npm run export-policy-data`). Verify + the policy appears in `policyData.jsonc`. A transport-only control has no policy + catalog entry, so it does not require a policy-data export. ### Transport-only control: `forceRemoteSettingsRefresh` diff --git a/.github/skills/add-policy/legacy-permission-policy.md b/.github/skills/add-policy/legacy-permission-policy.md new file mode 100644 index 0000000000000..bf10ce38ebca8 --- /dev/null +++ b/.github/skills/add-policy/legacy-permission-policy.md @@ -0,0 +1,29 @@ +# Pre-1.133 Permission-Policy Migration + +This is a temporary compatibility path for existing permission policies introduced +before VS Code 1.133.0. The migration remains in progress. + +Do not use this path for new controls. + +## Rules + +- Translate only exact enterprise `policyValue`; never user/workspace values. +- Map only to an equivalent runtime capability. +- Preserve restrictive semantics; do not synthesize permissive authorization. +- The compatibility bridge may emit managed rules, but the runtime remains the grammar + and enforcement authority. +- Validate emitted rules across the real SDK/runtime boundary. +- Retain existing enforcement until the SDK/runtime replacement is effective. +- Cover local create/resume, omission/removal, default and peer sessions, and diagnostics. + +Known facts to reverify while working here: + +- Kind-only `Shell` is the all-shell form; `Shell(*)` is invalid managed-rule syntax. +- Agent Host custom terminal tools arrive as `custom-tool`, not `shell`. +- AHP `permissions.deny` exists but is not enforced by the current host permission manager. +- Managed Tool and MCP tool-call coverage is evolving. +- Hook permission requests are outside the currently managed request families. +- Account-scoped managed-settings diagnostics omit the session-local injected layer. + +Do not expand this bridge into a general translation framework. New enterprise controls +belong in the shared managed-settings/SDK model. diff --git a/.github/skills/add-policy/mixed-policy.md b/.github/skills/add-policy/mixed-policy.md new file mode 100644 index 0000000000000..c2967c51395f4 --- /dev/null +++ b/.github/skills/add-policy/mixed-policy.md @@ -0,0 +1,28 @@ +# Split Runtime/Editor Control + +Use this path only when one enterprise control governs two independent behaviors: + +1. agent behavior executed inside the runtime; and +2. editor/workbench behavior implemented by VS Code. + +## Separation + +- Add the authoritative managed setting and enforcement in the runtime. +- Add a VS Code policy only for the editor-owned behavior. +- Keep both projections semantically aligned. +- Do not duplicate runtime matching or enforcement in VS Code. +- Do not use editor UI suppression as a substitute for runtime enforcement. + +If the runtime lacks an exact capability, add it first and retain existing editor +behavior until the authoritative replacement exists. + +## Integration Checks + +- Permission authorization remains separate from sandbox containment. +- Create, resume, removal, peer, subagent, and cloud behavior are explicit. +- Runtime-effective diagnostics include all enforced layers, or the gap is documented. + +Follow both: + +- [SDK/runtime managed setting](./sdk-runtime-policy.md) +- [VS Code configuration policy](./vscode-policy.md) diff --git a/.github/skills/add-policy/sdk-runtime-policy.md b/.github/skills/add-policy/sdk-runtime-policy.md new file mode 100644 index 0000000000000..abd03d5265540 --- /dev/null +++ b/.github/skills/add-policy/sdk-runtime-policy.md @@ -0,0 +1,71 @@ +# SDK/Runtime Managed Setting + +Use this path when policy governs behavior implemented inside the Copilot runtime: + +- tools, shell, files, URLs, MCP, plugins, or subagents; +- permissions, approvals, or sandboxing; +- runtime-owned model, telemetry, or remote-agent behavior. + +## Contract + +The runtime owns: + +- managed-settings schema and rule grammar; +- restrictive composition across enterprise sources; +- enforcement immediately before side effects; +- managed-ask one-time semantics; +- effective-policy and enforcement events; +- public SDK types when an integrator must supply or observe the control. + +Prefer runtime discovery of managed settings. Add a host-supplied SDK contract only when +the host genuinely owns the value. + +Do not add a VS Code `policy:` merely to mirror runtime policy. +Do not add a GitHub-token/account-policy field as a substitute for managed settings. + +## Permissions + +- Managed `deny` / `ask` / `allow` are runtime policy. +- AHP `{ allow, deny }` contains tool-name client preferences; it is not the managed DSL. +- New VS Code/AHP code transports managed rules opaquely and does not parse or match them. +- `managedApprovalRequired` bypasses all automatic and persistent approval paths. +- Managed approval is human-only and one-time-only. +- Permission authorization never widens sandbox access. +- Validate rule grammar through the real SDK/runtime boundary. + +## Host-Injection Lifecycle (In Progress) + +The runtime host-injection capability is still being adopted by the public SDK and VS +Code. When an integrator uses it, host-injected managed settings are startup +configuration: + +- supply them on local create and resume; +- re-supply them because they are not persisted; +- omission clears the previous injected layer; +- refresh default and peer sessions before the next turn when policy changes; +- reject unsupported cloud use instead of ignoring it. + +Policy removal must survive real JSON/AHP serialization. + +Client-injected settings are strict: malformed or unsupported rules reject create/resume. +Server/device discovery instead uses its defined cache and fail-open/fail-closed +degradation behavior. + +The runtime composes managed sandbox floors. While sandbox configuration remains +host-driven, verify that Agent Host applies the effective floor to session +`sandboxConfig`; policy state and containment must not diverge. + +Do not log raw enterprise rules or values. + +## Tests + +- Runtime: schema, parsing, matching, composition, revalidation, and pre-side-effect deny. +- SDK: create/resume serialization, events, handler safety, and cloud rejection. +- Agent Host E2E: generated grammar, managed asks, removal/resume, and diagnostics. + +Start with: + +- `github/copilot-agent-runtime/schema/managed-settings-schema.json` +- `github/copilot-agent-runtime/src/runtime/src/permissions/managed.rs` +- `github/copilot-agent-runtime/src/runtime/src/permissions/orchestrator.rs` +- `github/copilot-sdk/nodejs/src/types.ts` diff --git a/.github/skills/add-policy/vscode-policy.md b/.github/skills/add-policy/vscode-policy.md new file mode 100644 index 0000000000000..8e26332af2e2f --- /dev/null +++ b/.github/skills/add-policy/vscode-policy.md @@ -0,0 +1,96 @@ +# VS Code Configuration Policy + +Use this path only when the governed behavior belongs to the editor/workbench, such as +editor UI, updates, extension management, or other VS Code-native behavior. + +## Delivery + +VS Code configuration policy can arrive from: + +- OS policy (`NativePolicyService` on Windows/macOS, `FilePolicyService` on Linux); +- existing GitHub account data (`AccountPolicyService` via `IPolicyData`, deprecated for + new properties); +- Copilot managed-settings projection (native MDM > server > file, per key). + +`MultiplexPolicyService` combines the applicable policy services. Managed-settings keys +must be declared before they can project into VS Code configuration. + +## Declaration + +Find the configuration registration, typically in a `*.contribution.ts` file, and add: + +```typescript +policy: { + name: 'MyPolicyName', + category: PolicyCategory.InteractiveSession, + minimumVersion: '1.133', + localization: { + description: { + key: 'my.config.key.policy', + value: nls.localize('my.config.key.policy', "Policy description."), + }, + }, +} +``` + +Rules: + +- Use a unique PascalCase `name`. +- Derive `minimumVersion` from root `package.json` major/minor. +- Import `PolicyCategory` from `vs/base/common/policy` and use an existing category + (`Extensions`, `IntegratedTerminal`, `InteractiveSession`, `Telemetry`, or `Update`) + unless a new category and its localization are necessary. +- Localize all text. +- For string enums, provide one localized `enumDescriptions` entry per value. +- Preserve existing `value(policyData)` callbacks when maintaining historical GitHub + account policies; return the restrictive value to override and `undefined` otherwise. +- Read [github-managed-settings.md](./github-managed-settings.md) before projecting a + managed-settings key into VS Code configuration. + +Do not add new GitHub-token entitlement or policy fields to `IPolicyData` / +`AccountPolicyService`. Use managed settings for new Copilot enterprise controls. + +### Existing GitHub preview-feature policies + +Some existing preview-feature policies use the historical +`policyData.chat_preview_features_enabled` token entitlement. When maintaining one, +compare with `=== false` (not negation), return the setting's restrictive value when +false, and return `undefined` otherwise. Do not use this pattern for a new control. + +If one policy controls multiple VS Code settings, one setting owns `policy`; the others +use `policyReference: { name }`. + +- A setting cannot declare both `policy` and `policyReference`. +- Exactly one setting owns the policy metadata. +- Reference-setting and owner types must match. +- References contribute no `value`, managed-settings declarations, restricted value, + localization, or other policy metadata. + +## Required Procedure + +1. Add/update the policy and focused value/projection tests. +2. Check the build watch task or run the smallest relevant type check/test + (`npm run typecheck-client` when needed). +3. Run `npm run export-policy-data`. +4. Verify the policy appears and include `build/lib/policies/policyData.jsonc`. + +Never edit or synthesize `policyData.jsonc`, and never invoke a single product +entrypoint's `--export-policy-data` directly. Run the npm command from the worktree +containing the source change; it exports both Workbench and the Agents window, detects +conflicting policy metadata, and produces the complete catalog. + +The blocking +`src/vs/workbench/contrib/policyExport/test/node/policyExport.integrationTest.ts` uses +this canonical export path in check mode. + +Key sources: + +- `src/vs/base/common/policy.ts` +- `src/vs/base/common/defaultAccount.ts` +- `src/vs/platform/policy/common/policy.ts` +- `src/vs/platform/configuration/common/configurationRegistry.ts` +- `src/vs/platform/configuration/common/configurations.ts` +- `src/vs/platform/policy/common/copilotManagedSettings.ts` +- `src/vs/workbench/services/policies/common/accountPolicyService.ts` +- `build/lib/policies/policyGenerator.ts` +- `build/lib/test/policyConversion.test.ts` From 020eede634a84532681671f6bd22f8f89869f8eb Mon Sep 17 00:00:00 2001 From: Josh Spicer <23246594+joshspicer@users.noreply.github.com> Date: Thu, 6 Aug 2026 15:42:30 -0700 Subject: [PATCH 2/3] docs: broaden enterprise policy skill trigger Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/add-policy/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/skills/add-policy/SKILL.md b/.github/skills/add-policy/SKILL.md index 97c7762d4ed9e..34a581a3b9cf4 100644 --- a/.github/skills/add-policy/SKILL.md +++ b/.github/skills/add-policy/SKILL.md @@ -1,6 +1,6 @@ --- name: add-policy -description: Use when adding, modifying, or reviewing enterprise controls for VS Code or the GitHub Copilot SDK/runtime. Start here to decide whether policy belongs in runtime managed settings, VS Code configuration policy, extension policy, or a split implementation. Run on ANY change that adds a `policy:` field. +description: Use whenever adding, modifying, or reviewing any Copilot, agent, LLM, AI, tool, permission, sandbox, MCP, model, telemetry, feature-gate, setting, configuration, or enterprise control—especially anything an organization or administrator may need to manage. Start here to decide whether it belongs in runtime managed settings, a typed SDK contract, VS Code configuration policy, extension policy, or a split implementation. Run on every new Copilot/agent/LLM control and ANY change that adds a `policy:` field. --- # Adding an Enterprise Policy From 42347506b1905f4fdf9f5b7a1c5970381260c2fd Mon Sep 17 00:00:00 2001 From: Josh Spicer <23246594+joshspicer@users.noreply.github.com> Date: Thu, 6 Aug 2026 15:45:43 -0700 Subject: [PATCH 3/3] docs: rename enterprise settings skill Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../skills/{add-policy => policy-and-managed-settings}/SKILL.md | 2 +- .../extension-policy.md | 0 .../github-managed-settings.md | 0 .../legacy-permission-policy.md | 0 .../local-testing.md | 0 .../{add-policy => policy-and-managed-settings}/mixed-policy.md | 0 .../sdk-runtime-policy.md | 0 .../vscode-policy.md | 0 .../workbench/services/policies/common/accountPolicyService.ts | 2 +- 9 files changed, 2 insertions(+), 2 deletions(-) rename .github/skills/{add-policy => policy-and-managed-settings}/SKILL.md (98%) rename .github/skills/{add-policy => policy-and-managed-settings}/extension-policy.md (100%) rename .github/skills/{add-policy => policy-and-managed-settings}/github-managed-settings.md (100%) rename .github/skills/{add-policy => policy-and-managed-settings}/legacy-permission-policy.md (100%) rename .github/skills/{add-policy => policy-and-managed-settings}/local-testing.md (100%) rename .github/skills/{add-policy => policy-and-managed-settings}/mixed-policy.md (100%) rename .github/skills/{add-policy => policy-and-managed-settings}/sdk-runtime-policy.md (100%) rename .github/skills/{add-policy => policy-and-managed-settings}/vscode-policy.md (100%) diff --git a/.github/skills/add-policy/SKILL.md b/.github/skills/policy-and-managed-settings/SKILL.md similarity index 98% rename from .github/skills/add-policy/SKILL.md rename to .github/skills/policy-and-managed-settings/SKILL.md index 34a581a3b9cf4..1613496a0752f 100644 --- a/.github/skills/add-policy/SKILL.md +++ b/.github/skills/policy-and-managed-settings/SKILL.md @@ -1,5 +1,5 @@ --- -name: add-policy +name: policy-and-managed-settings description: Use whenever adding, modifying, or reviewing any Copilot, agent, LLM, AI, tool, permission, sandbox, MCP, model, telemetry, feature-gate, setting, configuration, or enterprise control—especially anything an organization or administrator may need to manage. Start here to decide whether it belongs in runtime managed settings, a typed SDK contract, VS Code configuration policy, extension policy, or a split implementation. Run on every new Copilot/agent/LLM control and ANY change that adds a `policy:` field. --- diff --git a/.github/skills/add-policy/extension-policy.md b/.github/skills/policy-and-managed-settings/extension-policy.md similarity index 100% rename from .github/skills/add-policy/extension-policy.md rename to .github/skills/policy-and-managed-settings/extension-policy.md diff --git a/.github/skills/add-policy/github-managed-settings.md b/.github/skills/policy-and-managed-settings/github-managed-settings.md similarity index 100% rename from .github/skills/add-policy/github-managed-settings.md rename to .github/skills/policy-and-managed-settings/github-managed-settings.md diff --git a/.github/skills/add-policy/legacy-permission-policy.md b/.github/skills/policy-and-managed-settings/legacy-permission-policy.md similarity index 100% rename from .github/skills/add-policy/legacy-permission-policy.md rename to .github/skills/policy-and-managed-settings/legacy-permission-policy.md diff --git a/.github/skills/add-policy/local-testing.md b/.github/skills/policy-and-managed-settings/local-testing.md similarity index 100% rename from .github/skills/add-policy/local-testing.md rename to .github/skills/policy-and-managed-settings/local-testing.md diff --git a/.github/skills/add-policy/mixed-policy.md b/.github/skills/policy-and-managed-settings/mixed-policy.md similarity index 100% rename from .github/skills/add-policy/mixed-policy.md rename to .github/skills/policy-and-managed-settings/mixed-policy.md diff --git a/.github/skills/add-policy/sdk-runtime-policy.md b/.github/skills/policy-and-managed-settings/sdk-runtime-policy.md similarity index 100% rename from .github/skills/add-policy/sdk-runtime-policy.md rename to .github/skills/policy-and-managed-settings/sdk-runtime-policy.md diff --git a/.github/skills/add-policy/vscode-policy.md b/.github/skills/policy-and-managed-settings/vscode-policy.md similarity index 100% rename from .github/skills/add-policy/vscode-policy.md rename to .github/skills/policy-and-managed-settings/vscode-policy.md diff --git a/src/vs/workbench/services/policies/common/accountPolicyService.ts b/src/vs/workbench/services/policies/common/accountPolicyService.ts index 81dc15fb056c6..e0110cf1d9e5a 100644 --- a/src/vs/workbench/services/policies/common/accountPolicyService.ts +++ b/src/vs/workbench/services/policies/common/accountPolicyService.ts @@ -193,7 +193,7 @@ export class AccountPolicyService extends AbstractPolicyService implements IPoli // Per-key precedence: native MDM wins over the server-delivered channel, which in turn wins // over the file-based channel — but resolved key-by-key, so a key left unset by a higher // channel is still filled in by a lower one. A key locked by a higher channel cannot be - // overwritten. See `.github/skills/add-policy/github-managed-settings.md` for the rationale. + // overwritten. See `.github/skills/policy-and-managed-settings/github-managed-settings.md` for the rationale. const pick = pickManagedSettings(nativeManagedSettings, accountPolicyData?.managedSettings, fileManagedSettings); if (!accountPolicyData && pick.activeSources.length === 0) { return undefined;