Ver Fonte

refactor(plugins): name the optional bundles in the launcher, not the installation manifest

OPTIONAL_BUNDLES in app-boot lists the bundles the installation ships
switched off; the plugin manager, the default-product isolation gate,
the workspace constraints, and the packed-install check read that one
list instead of a dsh.optionalBundles field in apps/cli/package.json.
Yichen Jiang há 2 dias atrás
pai
commit
fa320209ab

+ 2 - 2
.agents/notes/implemented/process/2026-09-12-default-product-experimental-isolation.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-09-12-default-product-experimental-isolation.md
-2026-09-12-default-product-experimental-isolation.md: 5c0c0c1c3d194f792591c6eb54a56cea65519bbe
-2026-09-12-default-product-experimental-isolation.zh.md: b8f3d64cb0db41f645ea8ca70bfc4c2a098c904f
+2026-09-12-default-product-experimental-isolation.md: 5c2485e4f737b822088a680b0006f395a5479fa9
+2026-09-12-default-product-experimental-isolation.zh.md: 3a412a491ab8356a4e29d706fed55226dc0f6038

+ 1 - 1
.agents/notes/implemented/process/2026-09-12-default-product-experimental-isolation.md

@@ -10,7 +10,7 @@ Public npm availability does not make an experimental package part of the defaul
 
 ## Decision
 
-[`verify-default-product-isolation`](../../../../scripts/verify-default-product-isolation.ts) runs in static CI and package hygiene. It follows runtime dependencies, optional dependencies, and peers from every app and the Python runtime, resolves workspace and npm aliases, and identifies experimental packages by their npm prefix or repository directory. Publication denylist membership has no effect on this classification. The bundles the installation lists under `dsh.optionalBundles` are the one declared exception ([shipped optional bundles](2026-09-15-shipped-optional-bundles.md)).
+[`verify-default-product-isolation`](../../../../scripts/verify-default-product-isolation.ts) runs in static CI and package hygiene. It follows runtime dependencies, optional dependencies, and peers from every app and the Python runtime, resolves workspace and npm aliases, and identifies experimental packages by their npm prefix or repository directory. Publication denylist membership has no effect on this classification. The bundles the launcher names in `OPTIONAL_BUNDLES` are the one declared exception ([shipped optional bundles](2026-09-15-shipped-optional-bundles.md)).
 
 The source check also reads runtime imports in the selected packages, installation-owned profile bundle lists, bundle patches, shipped agent presets, and declared configuration trees. It loads the default Web layers with the production patch parser and composes them with the same patch engine used at boot. The effective rows and patched Include trees are checked, so an id-only patch cannot hide a replacement group's plugins. Disabled plugin rows remain checked; ordinary plugin configuration data is not interpreted as another Loader entry list. Missing default roots fail the check.
 

+ 1 - 1
.agents/notes/implemented/process/2026-09-12-default-product-experimental-isolation.zh.md

@@ -10,7 +10,7 @@ Status: implemented
 
 ## Decision
 
-[`verify-default-product-isolation`](../../../../scripts/verify-default-product-isolation.ts) 在静态 CI 和包 hygiene 中运行。它从所有应用与 Python runtime 出发,遍历运行时依赖、可选依赖和 peer,解析 workspace 与 npm 别名,并按 npm 前缀或仓库目录识别实验包。发布 denylist 的成员关系不影响此分类。安装在 `dsh.optionalBundles` 下列出的组合包是唯一声明的例外([随安装提供的可选组合包](2026-09-15-shipped-optional-bundles.zh.md))。
+[`verify-default-product-isolation`](../../../../scripts/verify-default-product-isolation.ts) 在静态 CI 和包 hygiene 中运行。它从所有应用与 Python runtime 出发,遍历运行时依赖、可选依赖和 peer,解析 workspace 与 npm 别名,并按 npm 前缀或仓库目录识别实验包。发布 denylist 的成员关系不影响此分类。启动器在 `OPTIONAL_BUNDLES` 里点名的组合包是唯一声明的例外([随安装提供的可选组合包](2026-09-15-shipped-optional-bundles.zh.md))。
 
 源码检查还读取所选包的运行时导入、安装自带的 profile bundle 列表、bundle patch、随产品提供的 Agent preset,以及声明的配置树。它使用生产 patch 解析器加载默认 Web 各层,并使用启动时的同一个 patch 引擎完成组合。检查对象包括最终 entry 和应用 patch 后的 Include 树,因此仅按 id 覆盖 group 的 patch 也无法隐藏替换后的插件。禁用的插件行仍纳入检查;普通插件配置数据不会被解释为另一个 Loader entry 列表。默认入口缺失会使检查失败。
 

+ 2 - 2
.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.md
-2026-09-15-shipped-optional-bundles.md: 9377eff36b90bda261abcf640fa8ff50efb3a6a9
-2026-09-15-shipped-optional-bundles.zh.md: bf1afeb127ad0d0998c975e08758208416bf9332
+2026-09-15-shipped-optional-bundles.md: 6de3eb7d39b86a2e5dd6db756d2bf6561aeb8ed4
+2026-09-15-shipped-optional-bundles.zh.md: 05c23c7c0cdd6a146b938370fec64377652e31d4

+ 4 - 2
.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.md

@@ -10,7 +10,7 @@ The Web plugin page manages only the bundles a person installed into the profile
 
 ## Decision
 
-The installation's manifest, `apps/cli/package.json`, lists under `dsh.optionalBundles` the bundles it ships for a person to switch on. Each must be a runtime dependency that declares `dsh.bundle.patch`, and no shipped profile template selects it. The plugin manager's `listBundles` reports such a bundle as `optional`: switched off until selected, never removable, resolved from the installation like any installation-supplied bundle. The Web plugin page lists optional bundles in a built-in group with an official tag beside the profile's own installed bundles.
+The launcher names in `OPTIONAL_BUNDLES` (`packages/boot/app-boot/src/profile.ts`, beside the profile templates) the bundles the installation ships for a person to switch on. Each must be a runtime dependency of `apps/cli` that declares `dsh.bundle.patch`, and no shipped profile template selects it. The plugin manager's `listBundles` reports such a bundle as `optional`: switched off until selected, never removable, resolved from the installation like any installation-supplied bundle. The Web plugin page lists optional bundles in a built-in group with an official tag beside the profile's own installed bundles.
 
 Default-product isolation keeps its rules with one declared exception: an optional bundle's dependency graph is outside the default product. The static gate skips the `dependencies` edge from `@deepseek-ai/dsh` to a listed bundle and still rejects a runtime import, a shipped composition, a preset, or a default template that names it, an experimental dependency the list does not name, and a listed name that is not a runtime dependency or not a bundle. The workspace-constraints check accepts the same `dependencies` edges and no other runtime section, and the packed-install release check skips them from the installed entry package while requiring each listed bundle to be installed.
 
@@ -20,7 +20,9 @@ Agent Teams and Auto review ship this way first, as `@deepseek-ai/dsh-experiment
 
 **A catalog of installable official bundles.** The page would offer names to install from the registry on demand. That keeps the installation unchanged but needs network access at the moment of switching on and a version pin per release.
 
-**A flag on the bundle package.** A `dsh.bundle.optional` declaration would let any published bundle claim a place in the installation; the installation's own list keeps the choice with the product.
+**A flag on the bundle package.** A `dsh.bundle.optional` declaration would let any published bundle claim a place in the installation; the launcher's own list keeps the choice with the product.
+
+**A list in the installation's manifest.** `dsh.optionalBundles` in `apps/cli/package.json` was the first form; the maintainers keep product decisions in code, where the list is typed, read once, and shared by the manager and the gates.
 
 ## Consequences
 

+ 4 - 2
.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.zh.md

@@ -10,7 +10,7 @@ Web 插件页只管理用户装进 profile 的组合包。像 Agent Teams 这样
 
 ## 决策
 
-安装的 manifest,即 `apps/cli/package.json`,在 `dsh.optionalBundles` 下列出它随附、供用户开启的组合包。每一个都必须是声明了 `dsh.bundle.patch` 的运行时依赖,且不被任何随附 profile 模板选中。插件管理器的 `listBundles` 把这类组合包报告为 `optional`:选中前保持关闭、永不可卸载、像其他安装提供的组合包一样从安装目录解析。Web 插件页把可选组合包放在带官方标签的内置分组里,与 profile 自己安装的组合包并列。
+启动器在 `OPTIONAL_BUNDLES`(`packages/boot/app-boot/src/profile.ts`,与 profile 模板并列)里点名安装随附、供用户开启的组合包。每一个都必须是 `apps/cli` 声明了 `dsh.bundle.patch` 的运行时依赖,且不被任何随附 profile 模板选中。插件管理器的 `listBundles` 把这类组合包报告为 `optional`:选中前保持关闭、永不可卸载、像其他安装提供的组合包一样从安装目录解析。Web 插件页把可选组合包放在带官方标签的内置分组里,与 profile 自己安装的组合包并列。
 
 默认产品隔离的规则保持不变,只声明一个例外:可选组合包的依赖图在默认产品之外。静态门禁跳过从 `@deepseek-ai/dsh` 到列表中组合包的 `dependencies` 边,仍然拒绝运行时 import、随附组合、preset 或默认模板对它的引用,拒绝列表没有点名的实验依赖,也拒绝不是运行时依赖或不是组合包的列表项。workspace 约束检查接受同样的 `dependencies` 边而不接受其他运行时依赖段;发布时的 packed-install 检查对已安装入口包跳过这些边,并要求列表中的每个组合包都已安装。
 
@@ -20,7 +20,9 @@ Agent Teams 与 Auto review 首先以这种方式交付,即 `@deepseek-ai/dsh-
 
 **可安装官方组合包目录。** 页面按需提供从注册表安装的包名。安装本身不变,但开启那一刻需要网络,并且每次发布都要钉一个版本。
 
-**组合包自身的标记。** `dsh.bundle.optional` 声明会让任何已发布的组合包都能自称随安装提供;由安装自己的列表来决定,选择权留在产品手里。
+**组合包自身的标记。** `dsh.bundle.optional` 声明会让任何已发布的组合包都能自称随安装提供;由启动器自己的列表来决定,选择权留在产品手里。
+
+**放在安装 manifest 里的列表。** `apps/cli/package.json` 的 `dsh.optionalBundles` 是最初的形式;维护者把产品决定放在代码里,列表有类型、只读一次,管理器与门禁共用。
 
 ## 影响
 

+ 0 - 5
apps/cli/package.json

@@ -25,11 +25,6 @@
         "path": "../../packages/preset/agent-presets/presets",
         "scanRoster": true
       }
-    ],
-    "optionalBundles": [
-      "@deepseek-ai/dsh-experimental-agent-team-profile",
-      "@deepseek-ai/dsh-experimental-agent-team-web-profile",
-      "@deepseek-ai/dsh-experimental-auto-review"
     ]
   },
   "license": "MIT",

+ 1 - 0
packages/boot/app-boot/src/index.ts

@@ -36,6 +36,7 @@ export {
   composeEntries,
   createProfileResolutionGeneration,
   DEFAULT_PROFILE_BUNDLES,
+  OPTIONAL_BUNDLES,
   healProfilesModuleFallback,
   healIsolatedProfileModuleFallback,
   unlinkProfileModuleFallback,

+ 12 - 0
packages/boot/app-boot/src/profile.ts

@@ -158,6 +158,18 @@ const INSTALLATION_OWNED_PROFILE_TUPLES: Record<string, readonly string[]> = {
 /** The bundle list a `dsh plugin` init uses for a name with no shipped template. */
 export const DEFAULT_PROFILE_BUNDLES: readonly string[] = ['@deepseek-ai/dsh-base']
 
+/**
+ * The bundles the dsh installation ships for a person to switch on: each a
+ * runtime dependency of the installation that declares `dsh.bundle.patch`,
+ * selected by no shipped template, and offered switched off by the plugin
+ * manager ([rationale](../../../../.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.md)).
+ */
+export const OPTIONAL_BUNDLES: readonly string[] = [
+  '@deepseek-ai/dsh-experimental-agent-team-profile',
+  '@deepseek-ai/dsh-experimental-agent-team-web-profile',
+  '@deepseek-ai/dsh-experimental-auto-review',
+]
+
 const PROFILE_PATCH_TEMPLATE = `# Your patch layer for this dsh profile, applied after every bundle layer:
 # a top-level YAML array of loader patch entries (id-targeted config
 # overrides, disables, and insert lists; \`!!js\` expressions allowed).

+ 2 - 2
packages/boot/plugin-manager/README.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write packages/boot/plugin-manager/README.md
-README.md: 245c7a8742a533eb63f5cf7d40b5d3ca7df88edc
-README.zh.md: 9e7c3972affa24c792c88514c1a13acaac6c74dd
+README.md: 1d4217a8992300061e12ab397c6879d5ddedd009
+README.zh.md: ba76024f4eadc4781b2c87afe35b8a0ae3112b78

+ 1 - 1
packages/boot/plugin-manager/README.md

@@ -39,7 +39,7 @@ A plugin toggle updates only `disabled` in the last matching override in the pro
 
 `inspect(spec)` reads what a spec names before anything installs: a registry name is asked of the registry through `pnpm view`, run in the profile directory so the same registry and proxy settings apply as to the install; an absolute path has its `package.json` read; a git address or tarball answers only its form. The answer carries the name, version, description, `dsh.title`, and whether the package declares a bundle, or a `problem`: `invalid-spec`, `already-installed`, `not-found`, `not-a-package`, `not-a-bundle`, `network`, or `unknown`. A caller's `signal` or `inspectTimeoutMs` ends the lookup.
 
-`installBundle` accepts a caller-generated `requestId`, under which `plugin-manager/install-log` streams each pnpm run's output and `plugin-manager/install-state` announces `installing`, `cancelling`, and `applying`. `cancelInstall(requestId)` stops the run and answers `cancelled` only after pnpm exited and the files are back, `too-late` once the bundle is being applied, and `not-running` for any other id; the install call then reports `application: 'cancelled'`. A run that fails, is cancelled, or adds a package without a bundle patch restores `package.json` and `pnpm-lock.yaml` as they were; `packageResult.kind` classifies a failed run from its exit and output, and `bundle` names the package a finished run added. `listBundles` carries each bundle's title and one-liner (`dsh.title` and `dsh.description`, else the package `description`), the rows its patch declares with their live entries, and the built-in rows it overrides; it lists the profile's own bundles, the bundles the installation supplies, and a selected name without a bundle patch as a `not-bundle` problem, while an unselected plain dependency is left out. A bundle the installation's manifest names under `dsh.optionalBundles` is `optional`: shipped switched off for the person to turn on, never removable, and selected by no shipped template ([rationale](../../../.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.md)). Every completed operation emits `plugin-manager/changed`; a patch generation applied outside the manager, by HMR's watcher after a CLI or hand edit, announces nothing, so the page learns of it on its next read.
+`installBundle` accepts a caller-generated `requestId`, under which `plugin-manager/install-log` streams each pnpm run's output and `plugin-manager/install-state` announces `installing`, `cancelling`, and `applying`. `cancelInstall(requestId)` stops the run and answers `cancelled` only after pnpm exited and the files are back, `too-late` once the bundle is being applied, and `not-running` for any other id; the install call then reports `application: 'cancelled'`. A run that fails, is cancelled, or adds a package without a bundle patch restores `package.json` and `pnpm-lock.yaml` as they were; `packageResult.kind` classifies a failed run from its exit and output, and `bundle` names the package a finished run added. `listBundles` carries each bundle's title and one-liner (`dsh.title` and `dsh.description`, else the package `description`), the rows its patch declares with their live entries, and the built-in rows it overrides; it lists the profile's own bundles, the bundles the installation supplies, and a selected name without a bundle patch as a `not-bundle` problem, while an unselected plain dependency is left out. A bundle the launcher's `OPTIONAL_BUNDLES` names is `optional`: shipped switched off for the person to turn on, never removable, and selected by no shipped template ([rationale](../../../.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.md)). Every completed operation emits `plugin-manager/changed`; a patch generation applied outside the manager, by HMR's watcher after a CLI or hand edit, announces nothing, so the page learns of it on its next read.
 
 When pnpm 11 blocks dependency scripts, the failed installation reports every pending package name in the profile under `pendingBuilds`, including names left by earlier attempts; a failed run restores `package.json` and `pnpm-lock.yaml` but deliberately not `pnpm-workspace.yaml`, where pnpm records them. The Web plugin page offers **Allow these scripts and retry**; the tool can grant permission on the user's behalf through `approvedBuilds` on `install_bundle`, after the user approves those scripts in the conversation. The service validates pending names; it does not verify conversation approval. Approval persists by package name in this profile, permits commands with the host user's permissions, and survives another installation failure. Only currently undecided names can be approved; existing denials and wildcard rules cannot be overridden through this action. Approval rejects YAML anchors or aliases inside `allowBuilds`. Retry preserves the original activation choice.
 

+ 1 - 1
packages/boot/plugin-manager/README.zh.md

@@ -39,7 +39,7 @@ kind: "package-reference"
 
 `inspect(spec)` 在任何东西安装之前读出 spec 指向什么:注册表包名通过 `pnpm view` 询问注册表,在 profile 目录中运行,因而与安装使用同样的注册表与代理设置;绝对路径读取其 `package.json`;git 地址或 tarball 只答复自己的形式。答复携带名称、版本、描述、`dsh.title` 以及该包是否声明组合包,否则给出 `problem`:`invalid-spec`、`already-installed`、`not-found`、`not-a-package`、`not-a-bundle`、`network` 或 `unknown`。调用方的 `signal` 或 `inspectTimeoutMs` 会结束查询。
 
-`installBundle` 接受调用方生成的 `requestId`,`plugin-manager/install-log` 在其下流式转发每次 pnpm 运行的输出,`plugin-manager/install-state` 通告 `installing`、`cancelling` 与 `applying`。`cancelInstall(requestId)` 停止运行,只在 pnpm 退出且文件恢复后答复 `cancelled`,组合包已在应用时答复 `too-late`,其他 id 答复 `not-running`;安装调用随后报告 `application: 'cancelled'`。失败、被取消或装入了没有组合包 patch 的包的运行,会把 `package.json` 与 `pnpm-lock.yaml` 恢复原样;`packageResult.kind` 按退出方式与输出对失败运行分类,`bundle` 给出完成的运行新增的包。`listBundles` 携带每个组合包的标题与一句话简介(`dsh.title` 与 `dsh.description`,没有则用包的 `description`)、其 patch 声明的行及其存活条目,以及它覆盖的内置行;它列出 profile 自己的组合包、安装提供的组合包,以及被选中却没有组合包 patch 的名字(作为 `not-bundle` 问题),未选中的普通依赖不列出。安装的 manifest 在 `dsh.optionalBundles` 下点名的组合包是 `optional`:随安装提供、默认关闭、由用户开启,永不可卸载,也不被任何随附模板选中([理由](../../../.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.zh.md))。每个完成的操作都会发出 `plugin-manager/changed`;在管理器之外应用的一代 patch(HMR 监视到 CLI 或手工编辑后)不发通知,页面要到下一次读取才知道。
+`installBundle` 接受调用方生成的 `requestId`,`plugin-manager/install-log` 在其下流式转发每次 pnpm 运行的输出,`plugin-manager/install-state` 通告 `installing`、`cancelling` 与 `applying`。`cancelInstall(requestId)` 停止运行,只在 pnpm 退出且文件恢复后答复 `cancelled`,组合包已在应用时答复 `too-late`,其他 id 答复 `not-running`;安装调用随后报告 `application: 'cancelled'`。失败、被取消或装入了没有组合包 patch 的包的运行,会把 `package.json` 与 `pnpm-lock.yaml` 恢复原样;`packageResult.kind` 按退出方式与输出对失败运行分类,`bundle` 给出完成的运行新增的包。`listBundles` 携带每个组合包的标题与一句话简介(`dsh.title` 与 `dsh.description`,没有则用包的 `description`)、其 patch 声明的行及其存活条目,以及它覆盖的内置行;它列出 profile 自己的组合包、安装提供的组合包,以及被选中却没有组合包 patch 的名字(作为 `not-bundle` 问题),未选中的普通依赖不列出。启动器的 `OPTIONAL_BUNDLES` 点名的组合包是 `optional`:随安装提供、默认关闭、由用户开启,永不可卸载,也不被任何随附模板选中([理由](../../../.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.zh.md))。每个完成的操作都会发出 `plugin-manager/changed`;在管理器之外应用的一代 patch(HMR 监视到 CLI 或手工编辑后)不发通知,页面要到下一次读取才知道。
 
 pnpm 11 拦下依赖脚本时,失败的安装在 `pendingBuilds` 里报告 profile 中所有待决定的包名,包括先前尝试留下的;失败的运行会恢复 `package.json` 与 `pnpm-lock.yaml`,但有意不恢复 pnpm 记录这些名字的 `pnpm-workspace.yaml`。Web 插件页提供**允许这些脚本并重试**;工具可以在用户于对话中批准这些脚本后,通过 `install_bundle` 的 `approvedBuilds` 代为授权。服务只校验待决定的名字,不核实对话中的批准。授权按包名保存在当前 profile,允许以宿主用户的权限执行命令,并在再次安装失败后保留。只能批准当前未决定的名字;已有的拒绝与通配规则不能通过此操作覆盖。`allowBuilds` 里出现 YAML 锚点或别名时拒绝授权。重试保留原来的启用选择。
 

+ 4 - 4
packages/boot/plugin-manager/src/index.ts

@@ -10,7 +10,9 @@ import type { PatchOptions } from '@deepseek-ai/cordis-plugin-include'
 import z from '@deepseek-ai/schemastery'
 import { TypertRemoteService, Remote } from '@deepseek-ai/dsh-typert-protocol'
 import { pluginEntryId, readPluginInventory } from '@deepseek-ai/dsh-host-plugin-inventory'
-import { readProfileManifest, resolveBundleDir, loadOverlayPatches, composeEntries, reconcileProfilePatches, readProfilePatches } from '@deepseek-ai/dsh-app-boot'
+import {
+  readProfileManifest, resolveBundleDir, loadOverlayPatches, composeEntries, reconcileProfilePatches, readProfilePatches, OPTIONAL_BUNDLES,
+} from '@deepseek-ai/dsh-app-boot'
 import type {} from '@deepseek-ai/dsh-hmr'
 import type { ProfileContext, ProfileManifest } from '@deepseek-ai/dsh-app-boot'
 import { bundleManifest, runProfilePnpm, saveManifest, viewProfilePackage } from './operations.ts'
@@ -96,7 +98,6 @@ function stringField(manifest: object, field: string): string | undefined {
 /** The fields of the dsh installation's own manifest the manager reads. */
 interface InstallationManifest {
   dependencies?: Record<string, string>
-  dsh?: { optionalBundles?: string[] }
 }
 
 /** What a package manifest says about the package: identity, one-liner, and whether it is a bundle. */
@@ -197,12 +198,11 @@ export class PluginManager extends TypertRemoteService {
     const selected = manifest.dsh?.profile?.bundles ?? []
     const dependencies = Object.keys(manifest.dependencies ?? {})
     const installation = JSON.parse(readFileSync(this.profile.installAnchor, 'utf8')) as InstallationManifest
-    const offered = installation.dsh?.optionalBundles ?? []
     const names = [...new Set([...selected, ...dependencies, ...Object.keys(installation.dependencies ?? {})])]
     const bundles: BundleInfo[] = []
     for (const name of names) {
       const installed = dependencies.includes(name)
-      const optional = offered.includes(name)
+      const optional = OPTIONAL_BUNDLES.includes(name)
       const removable = installed && !Object.hasOwn(installation.dependencies ?? {}, name)
       const enabled = selected.includes(name)
       try {

+ 2 - 2
packages/boot/plugin-manager/src/types.ts

@@ -41,8 +41,8 @@ export interface BundleInfo {
   /** Whether the profile's own dependencies hold the package; false for a bundle the dsh installation supplies. */
   installed: boolean
   /**
-   * Whether the installation ships the bundle for the person to switch on: named under `dsh.optionalBundles` in
-   * the installation's manifest, selected by no shipped template, and never removable.
+   * Whether the installation ships the bundle for the person to switch on: named by the launcher's `OPTIONAL_BUNDLES`,
+   * held by the installation's dependencies, selected by no shipped template, and never removable.
    */
   optional: boolean
   removable: boolean

+ 19 - 13
packages/boot/plugin-manager/tests/manager.spec.ts

@@ -7,7 +7,7 @@ import { pathToFileURL } from 'node:url'
 import type { Context } from '@deepseek-ai/cordis'
 import { expect, it, onTestFinished, vi } from 'vitest'
 import {
-  boot, composeEntries, initProfile, readProfilePatches, readProfileManifest, reconcileProfilePatches,
+  boot, composeEntries, initProfile, readProfilePatches, readProfileManifest, reconcileProfilePatches, OPTIONAL_BUNDLES,
   type ProfileContext,
 } from '@deepseek-ai/dsh-app-boot'
 import PluginManager, { type Config, type PluginChange, type PluginInstallLogChunk, type PluginInstallProgress, type PluginInstallRequestId } from '../src/index.ts'
@@ -676,22 +676,28 @@ it('refuses removal of a hot-installed bundle after HMR is disabled', async () =
   expect(await manager.removeBundle('later')).toMatchObject({ changed: false, application: 'failed' })
 })
 
-it('offers the installation\'s optional bundles switched off and never removable, preferring dsh.description', async () => {
-  const { manager, dir, profile, bundle } = await fixture()
-  bundle('offered', [{ id: 'offered-row', name: './plugin.mjs', config: { service: 'offeredProbe' } }])
-  writeFileSync(join(dir, 'node_modules', 'offered', 'package.json'), JSON.stringify({
-    name: 'offered', version: '3.0.0', description: 'Package one-liner.',
+it('offers the launcher\'s optional bundles switched off and never removable, preferring dsh.description', async () => {
+  const { manager, profile } = await fixture()
+  // The launcher names the bundles the installation ships; the fixture supplies one of them from the
+  // installation's own node_modules, which the resolver consults before the profile's and before the repository's.
+  const offered = OPTIONAL_BUNDLES[0]!
+  const supplied = join(profile.home, 'node_modules', offered)
+  mkdirSync(supplied, { recursive: true })
+  writeFileSync(join(supplied, 'package.json'), JSON.stringify({
+    name: offered, version: '3.0.0', description: 'Package one-liner.',
     dsh: { title: 'Offered', description: 'Display one-liner.', bundle: { patch: './cordis.patch.yml' } },
   }))
-  writeFileSync(profile.installAnchor, JSON.stringify({ name: 'installation', dependencies: { offered: '3.0.0' }, dsh: { optionalBundles: ['offered'] } }))
-  expect((await manager.listBundles()).find(row => row.name === 'offered')).toEqual({
-    name: 'offered', version: '3.0.0', title: 'Offered', description: 'Display one-liner.',
+  writeFileSync(join(supplied, 'cordis.patch.yml'), JSON.stringify([{ insert: [{ id: 'offered-row', name: './plugin.mjs', config: { service: 'offeredProbe' } }] }]))
+  writeFileSync(join(supplied, 'plugin.mjs'), 'export function apply(ctx, config) { ctx.provide(config?.service ?? "offeredProbe", true) }\n')
+  writeFileSync(profile.installAnchor, JSON.stringify({ name: 'installation', dependencies: { [offered]: '3.0.0' } }))
+  expect((await manager.listBundles()).find(row => row.name === offered)).toEqual({
+    name: offered, version: '3.0.0', title: 'Offered', description: 'Display one-liner.',
     enabled: false, installed: false, optional: true, removable: false,
-    rows: [{ rowId: 'offered-row', moduleName: pathToFileURL(join(dir, 'node_modules', 'offered', 'plugin.mjs')).href }], overrides: [],
+    rows: [{ rowId: 'offered-row', moduleName: pathToFileURL(join(supplied, 'plugin.mjs')).href }], overrides: [],
   })
-  expect(await manager.setBundleEnabled('offered', true)).toMatchObject({ application: 'applied' })
-  expect((await manager.listBundles()).find(row => row.name === 'offered')).toMatchObject({ enabled: true, optional: true, removable: false })
-  expect(await manager.removeBundle('offered')).toMatchObject({ changed: false, application: 'failed' })
+  expect(await manager.setBundleEnabled(offered, true)).toMatchObject({ application: 'applied' })
+  expect((await manager.listBundles()).find(row => row.name === offered)).toMatchObject({ enabled: true, optional: true, removable: false })
+  expect(await manager.removeBundle(offered)).toMatchObject({ changed: false, application: 'failed' })
 })
 
 it('omits installation-owned plain packages from the bundle inventory', async () => {

+ 6 - 9
scripts/check-workspace-constraints.spec.ts

@@ -88,21 +88,18 @@ describe('experimental workspace constraints', () => {
     },
   )
 
-  it('allows the dsh installation to ship the optional bundles it lists, and nothing else experimental', () => {
-    const installation = (dependencies: Record<string, string>, optionalBundles: unknown) => ({
-      dir: 'apps/cli',
-      manifest: { name: '@deepseek-ai/dsh', dependencies, dsh: { optionalBundles } },
-    }) satisfies WorkspaceManifest
+  it('allows the dsh installation to ship the optional bundles the launcher names, and nothing else experimental', () => {
     const listed = { '@deepseek-ai/dsh-experimental-prototype': 'workspace:^' }
-    expect(checkExperimentalDependencyIsolation([experimental, installation(listed, ['@deepseek-ai/dsh-experimental-prototype'])])).toEqual([])
-    expect(checkExperimentalDependencyIsolation([experimental, installation(listed, [])])).toEqual([
+    const installation = { dir: 'apps/cli', manifest: { name: '@deepseek-ai/dsh', dependencies: listed } } satisfies WorkspaceManifest
+    expect(checkExperimentalDependencyIsolation([experimental, installation], ['@deepseek-ai/dsh-experimental-prototype'])).toEqual([])
+    expect(checkExperimentalDependencyIsolation([experimental, installation], [])).toEqual([
       '@deepseek-ai/dsh: dependencies.@deepseek-ai/dsh-experimental-prototype must not reference an experimental package',
     ])
     // Only a plain dependency edge is offered; a peer would make the bundle a requirement of every consumer.
     expect(checkExperimentalDependencyIsolation([experimental, {
       dir: 'apps/cli',
-      manifest: { name: '@deepseek-ai/dsh', peerDependencies: listed, dsh: { optionalBundles: ['@deepseek-ai/dsh-experimental-prototype'] } },
-    }])).toEqual([
+      manifest: { name: '@deepseek-ai/dsh', peerDependencies: listed },
+    }], ['@deepseek-ai/dsh-experimental-prototype'])).toEqual([
       '@deepseek-ai/dsh: peerDependencies.@deepseek-ai/dsh-experimental-prototype must not reference an experimental package',
     ])
   })

+ 7 - 7
scripts/check-workspace-constraints.ts

@@ -13,6 +13,7 @@ import {
   PRIVATE_EXPERIMENTAL_PACKAGE_DIRECTORIES,
 } from './experimental-package-policy.ts'
 import { hasTypertRemoteNavigation, isForbiddenPublicationFile } from './publication-payload.ts'
+import { OPTIONAL_BUNDLES } from '../packages/boot/app-boot/src/profile.ts'
 import { collectProjectReferenceFaceViolations } from './project-reference-faces.ts'
 
 const root = resolve(import.meta.dirname, '..')
@@ -99,8 +100,6 @@ export interface PackageManifest {
     bundle?: {
       patch?: string
     }
-    /** The bundles the dsh installation ships switched off; the one runtime edge to experimental packages it may hold. */
-    optionalBundles?: unknown
   }
 }
 
@@ -505,12 +504,15 @@ const runtimeDependencySections = ['dependencies', 'optionalDependencies', 'peer
 
 /**
  * Prevent an official runtime from requiring an experimental package. The dsh installation's `dependencies`
- * may hold the bundles its `dsh.optionalBundles` lists: shipped switched off, they are not a requirement
+ * may hold the bundles the launcher's `OPTIONAL_BUNDLES` names: shipped switched off, they are not a requirement
  * ([rationale](../.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.md)).
  * @param manifests - release, private experimental, and deployment-root manifests.
+ * @param optionalBundles - the bundles the installation ships switched off; the launcher's list by default.
  * @returns One error for each forbidden runtime dependency.
  */
-export function checkExperimentalDependencyIsolation(manifests: readonly WorkspaceManifest[]): string[] {
+export function checkExperimentalDependencyIsolation(
+  manifests: readonly WorkspaceManifest[], optionalBundles: readonly string[] = OPTIONAL_BUNDLES,
+): string[] {
   const experimentalNames = new Set(manifests
     .filter(entry => experimentalPackageDirectory.test(entry.dir))
     .map(entry => entry.manifest.name)
@@ -518,9 +520,7 @@ export function checkExperimentalDependencyIsolation(manifests: readonly Workspa
   const errors: string[] = []
   for (const { dir, manifest } of manifests) {
     if (!standardReleaseMemberDirectory.test(dir) && dir !== 'python/sdk-runtime') continue
-    const offered = manifest.name === '@deepseek-ai/dsh' && Array.isArray(manifest.dsh?.optionalBundles)
-      ? new Set(manifest.dsh.optionalBundles.filter((name): name is string => typeof name === 'string'))
-      : new Set<string>()
+    const offered = manifest.name === '@deepseek-ai/dsh' ? new Set(optionalBundles) : new Set<string>()
     for (const section of runtimeDependencySections) {
       for (const name of Object.keys(manifest[section] ?? {})) {
         if (!experimentalNames.has(name)) continue

+ 5 - 12
scripts/release/installed-product-isolation.spec.ts

@@ -54,23 +54,16 @@ describe('installed default-product isolation', () => {
 
   it('skips the entry package\'s optional bundles, which must be installed, and still rejects other experimental edges', () => {
     const root = fixture()
-    const entry = writePackage(root, '@deepseek-ai/dsh', {
-      dependencies: { core: '1.0.0', [experimental]: '1.0.0' },
-      dsh: { optionalBundles: [experimental] },
-    })
+    const entry = writePackage(root, '@deepseek-ai/dsh', { dependencies: { core: '1.0.0', [experimental]: '1.0.0' } })
     writePackage(root, 'core', {})
     writePackage(root, experimental, { dependencies: { '@deepseek-ai/dsh-experimental-inner': '1.0.0' } })
-    expect(verifyInstalledProductIsolation(entry)).toBe(2)
+    expect(verifyInstalledProductIsolation(entry, [experimental])).toBe(2)
 
     writePackage(root, 'core', { dependencies: { [experimental]: '1.0.0' } })
-    expect(() => verifyInstalledProductIsolation(entry)).toThrow(`core -> ${experimental}`)
+    expect(() => verifyInstalledProductIsolation(entry, [experimental])).toThrow(`core -> ${experimental}`)
 
-    const missing = writePackage(root, '@deepseek-ai/dsh-missing', {
-      dependencies: { absent: '1.0.0' }, dsh: { optionalBundles: ['absent'] },
-    })
-    expect(() => verifyInstalledProductIsolation(missing)).toThrow('optional bundle is missing: @deepseek-ai/dsh-missing -> absent')
-    const malformed = writePackage(root, '@deepseek-ai/dsh-malformed', { dsh: { optionalBundles: 'absent' } })
-    expect(() => verifyInstalledProductIsolation(malformed)).toThrow('dsh.optionalBundles must be a list of package names')
+    const missing = writePackage(root, '@deepseek-ai/dsh-missing', { dependencies: { absent: '1.0.0' } })
+    expect(() => verifyInstalledProductIsolation(missing, ['absent'])).toThrow('optional bundle is missing: @deepseek-ai/dsh-missing -> absent')
   })
 
   it('rejects experimental identities hidden behind an installed alias', () => {

+ 4 - 14
scripts/release/installed-product-isolation.ts

@@ -3,13 +3,13 @@
 import { existsSync, readFileSync, realpathSync } from 'node:fs'
 import { createRequire } from 'node:module'
 import { join } from 'node:path'
+import { OPTIONAL_BUNDLES } from '../../packages/boot/app-boot/src/profile.ts'
 
 interface InstalledManifest {
   name: string
   dependencies?: Record<string, string>
   optionalDependencies?: Record<string, string>
   peerDependencies?: Record<string, string>
-  dsh?: { optionalBundles?: unknown }
   peerDependenciesMeta?: Record<string, { optional?: boolean }>
 }
 
@@ -19,7 +19,7 @@ interface InstalledManifest {
  * @param directory - installed entry package directory.
  * @returns number of distinct installed packages visited.
  */
-export function verifyInstalledProductIsolation(directory: string): number {
+export function verifyInstalledProductIsolation(directory: string, optionalBundles: readonly string[] = OPTIONAL_BUNDLES): number {
   const visited = new Set<string>()
   const queue = [{ directory, chain: [] as string[] }]
   for (const item of queue) {
@@ -30,10 +30,10 @@ export function verifyInstalledProductIsolation(directory: string): number {
     const chain = [...item.chain, manifest.name]
     rejectExperimental(manifest.name, chain)
     // The bundles the entry package ships switched off are installed beside the product, not required by it.
-    const optionalBundles = item.chain.length === 0 ? optionalBundlesOf(manifest) : new Set<string>()
+    const offered = item.chain.length === 0 ? new Set(optionalBundles) : new Set<string>()
     for (const section of ['dependencies', 'optionalDependencies', 'peerDependencies'] as const) {
       for (const [name, range] of Object.entries(manifest[section] ?? {})) {
-        if (section === 'dependencies' && optionalBundles.has(name)) {
+        if (section === 'dependencies' && offered.has(name)) {
           if (installedPackage(canonical, name) === undefined) throw new Error(`optional bundle is missing: ${[...chain, name].join(' -> ')}`)
           continue
         }
@@ -55,16 +55,6 @@ export function verifyInstalledProductIsolation(directory: string): number {
   return visited.size
 }
 
-/** The names under the entry package's `dsh.optionalBundles`, the shipped bundles no default composition selects. */
-function optionalBundlesOf(manifest: InstalledManifest): Set<string> {
-  const offered = manifest.dsh?.optionalBundles
-  if (offered === undefined) return new Set()
-  if (!Array.isArray(offered) || !offered.every(name => typeof name === 'string')) {
-    throw new Error(`${manifest.name}: dsh.optionalBundles must be a list of package names`)
-  }
-  return new Set(offered)
-}
-
 function rejectExperimental(name: string, chain: readonly string[]): void {
   if (name.startsWith('@deepseek-ai/dsh-experimental-')) {
     throw new Error(`default product includes an experimental package: ${chain.join(' -> ')}`)

+ 13 - 10
scripts/verify-default-product-isolation.spec.ts

@@ -76,9 +76,10 @@ describe('default product isolation', () => {
       name: layer, dependencies: { [experimental]: 'workspace:^' }, dsh: { bundle: { patch: './cordis.patch.yml' } },
     })
     write(root, 'packages/experimental/layer/cordis.patch.yml', [{ insert: [{ name: experimental }] }])
-    manifest(root, 'apps/cli/package.json', {
-      dependencies: { [core]: 'workspace:^', [layer]: 'workspace:^' }, dsh: { optionalBundles: [layer] },
-    })
+    manifest(root, 'apps/cli/package.json', { dependencies: { [core]: 'workspace:^', [layer]: 'workspace:^' } })
+    write(root, profile, `export const PROFILE_TEMPLATES = { web: { bundles: ['${base}'] } }\n`
+      + `export const DEFAULT_PROFILE_BUNDLES = ['${base}']\n`
+      + `export const OPTIONAL_BUNDLES = ['${layer}']\n`)
     expect(verifyDefaultProductIsolation(root)).toMatchObject({ failures: [], packageCount: 5 })
 
     // The exception covers the dependency edge alone: a runtime import or a default template still names the product.
@@ -86,22 +87,24 @@ describe('default product isolation', () => {
     expect(verifyDefaultProductIsolation(root).failures.join('\n')).toContain(`apps/cli/src/bin.ts -> ${layer}`)
     write(root, 'apps/cli/src/bin.ts', 'export {}\n')
     write(root, profile, `export const PROFILE_TEMPLATES = { web: { bundles: ['${base}', '${layer}'] } }\n`
-      + `export const DEFAULT_PROFILE_BUNDLES = ['${base}']\n`)
+      + `export const DEFAULT_PROFILE_BUNDLES = ['${base}']\n`
+      + `export const OPTIONAL_BUNDLES = ['${layer}']\n`)
     expect(verifyDefaultProductIsolation(root).failures.join('\n')).toContain(`optional bundle ${layer} must not be a default bundle`)
   })
 
   it('requires each optional bundle to be a runtime dependency that declares a bundle patch', () => {
     const root = fixture()
-    manifest(root, 'apps/cli/package.json', { dsh: { optionalBundles: [experimental] } })
+    write(root, profile, `export const PROFILE_TEMPLATES = { web: { bundles: ['${base}'] } }\n`
+      + `export const DEFAULT_PROFILE_BUNDLES = ['${base}']\n`
+      + `export const OPTIONAL_BUNDLES = ['${experimental}']\n`)
     const failures = verifyDefaultProductIsolation(root).failures.join('\n')
-    expect(failures).toContain(`optional bundle ${experimental} must be a runtime dependency`)
+    expect(failures).toContain(`optional bundle ${experimental} must be a runtime dependency of apps/cli`)
     expect(failures).toContain(`optional bundle ${experimental} must declare dsh.bundle.patch`)
 
-    manifest(root, 'apps/cli/package.json', { dsh: { optionalBundles: 'not-a-list' } })
-    expect(verifyDefaultProductIsolation(root).failures).toContain('apps/cli/package.json: dsh.optionalBundles must be a list of package names')
-
     // An experimental runtime dependency the list does not name is still a product requirement.
-    manifest(root, 'apps/cli/package.json', { dependencies: { [core]: 'workspace:^', [experimental]: 'workspace:^' }, dsh: {} })
+    write(root, profile, `export const PROFILE_TEMPLATES = { web: { bundles: ['${base}'] } }\n`
+      + `export const DEFAULT_PROFILE_BUNDLES = ['${base}']\n`)
+    manifest(root, 'apps/cli/package.json', { dependencies: { [core]: 'workspace:^', [experimental]: 'workspace:^' } })
     expect(verifyDefaultProductIsolation(root).failures.join('\n')).toContain(`@deepseek-ai/dsh dependencies -> ${experimental}`)
   })
 

+ 25 - 28
scripts/verify-default-product-isolation.ts

@@ -1,7 +1,7 @@
 /**
  * Keep experimental packages outside default installations, runtime imports, and shipped compositions.
- * The one declared exception is a bundle the installation lists under `dsh.optionalBundles`: shipped for the
- * person to switch on, selected by no shipped template, its own dependency graph outside the default product's.
+ * The one declared exception is a bundle the launcher names in `OPTIONAL_BUNDLES`: shipped for the person to
+ * switch on, selected by no shipped template, its own dependency graph outside the default product's.
  */
 
 import { existsSync, globSync, readFileSync, statSync } from 'node:fs'
@@ -30,7 +30,7 @@ interface Manifest {
   optionalDependencies?: Record<string, string>
   peerDependencies?: Record<string, string>
   devDependencies?: Record<string, string>
-  dsh?: { bundle?: { patch?: string }; configTrees?: Array<{ path: string }>; optionalBundles?: unknown }
+  dsh?: { bundle?: { patch?: string }; configTrees?: Array<{ path: string }> }
 }
 
 interface Package {
@@ -76,22 +76,16 @@ export function verifyDefaultProductIsolation(root: string): ProductIsolationRes
   if (cli?.manifest.name !== '@deepseek-ai/dsh') {
     failures.push('apps/cli/package.json must identify @deepseek-ai/dsh')
   }
-  // The bundles the installation ships switched off: each a runtime dependency that is a bundle, none a default.
-  const optionalBundles = new Set<string>()
-  const offered = cli?.manifest.dsh?.optionalBundles
-  if (offered !== undefined) {
-    if (!Array.isArray(offered) || !offered.every(name => typeof name === 'string')) {
-      failures.push('apps/cli/package.json: dsh.optionalBundles must be a list of package names')
-    } else {
-      for (const name of offered) {
-        if (cli?.manifest.dependencies?.[name] === undefined) {
-          failures.push(`apps/cli/package.json: optional bundle ${name} must be a runtime dependency`)
-        }
-        if (packages.get(name)?.manifest.dsh?.bundle?.patch === undefined) {
-          failures.push(`apps/cli/package.json: optional bundle ${name} must declare dsh.bundle.patch`)
-        }
-        optionalBundles.add(name)
-      }
+  // The bundles the launcher ships switched off: each a runtime dependency of the installation that is a bundle, none a default.
+  const profilePath = resolve(root, PROFILE_SOURCE)
+  const selection = existsSync(profilePath) ? profilePackages(readFileSync(profilePath, 'utf8')) : undefined
+  const optionalBundles = new Set(selection?.optionalBundles ?? [])
+  for (const name of optionalBundles) {
+    if (cli?.manifest.dependencies?.[name] === undefined) {
+      failures.push(`${PROFILE_SOURCE}: optional bundle ${name} must be a runtime dependency of apps/cli`)
+    }
+    if (packages.get(name)?.manifest.dsh?.bundle?.patch === undefined) {
+      failures.push(`${PROFILE_SOURCE}: optional bundle ${name} must declare dsh.bundle.patch`)
     }
   }
 
@@ -231,9 +225,7 @@ export function verifyDefaultProductIsolation(root: string): ProductIsolationRes
     const path = display(pkg.directory)
     if (path.startsWith('apps/') || path === 'python/sdk-runtime') add(pkg, path)
   }
-  const profilePath = resolve(root, PROFILE_SOURCE)
-  if (existsSync(profilePath)) {
-    const selection = profilePackages(readFileSync(profilePath, 'utf8'))
+  if (selection !== undefined) {
     for (const name of selection.packages) {
       reference(name, PROFILE_SOURCE)
       if (packages.get(name)?.manifest.dsh?.bundle?.patch === undefined) {
@@ -347,16 +339,18 @@ function isRecord(value: unknown): value is Record<string, unknown> {
   return typeof value === 'object' && value !== null && !Array.isArray(value)
 }
 
-/** Read the literal package lists that define installation-owned profile defaults. */
-function profilePackages(source: string): { packages: string[]; webBundles: string[] } {
+/** Read the literal package lists that define installation-owned profile defaults and the optional bundles it ships. */
+function profilePackages(source: string): { packages: string[]; webBundles: string[]; optionalBundles: string[] } {
   const file = ts.createSourceFile(PROFILE_SOURCE, source, ts.ScriptTarget.Latest, true)
   const required = new Set(['PROFILE_TEMPLATES', 'DEFAULT_PROFILE_BUNDLES'])
   const found = new Set<string>()
   const packages: string[] = []
   const webBundles: string[] = []
+  const optionalBundles: string[] = []
   const literals = (node: ts.Node, path: string[]): void => {
     if (ts.isStringLiteralLike(node)) {
-      if (node.text.startsWith('@')) packages.push(node.text)
+      if (path[0] === 'OPTIONAL_BUNDLES') optionalBundles.push(node.text)
+      else if (node.text.startsWith('@')) packages.push(node.text)
       if (path.join('.') === 'PROFILE_TEMPLATES.web.bundles') webBundles.push(node.text)
     } else if (ts.isArrayLiteralExpression(node)) node.elements.forEach((child) => { literals(child, path) })
     else if (ts.isObjectLiteralExpression(node)) node.properties.forEach((child) => { literals(child, path) })
@@ -374,17 +368,20 @@ function profilePackages(source: string): { packages: string[]; webBundles: stri
     if (!ts.isVariableStatement(statement)) continue
     for (const declaration of statement.declarationList.declarations) {
       if (!ts.isIdentifier(declaration.name)
-        || !required.has(declaration.name.text) && declaration.name.text !== 'INSTALLATION_OWNED_PROFILE_TUPLES') continue
+        || !required.has(declaration.name.text) && declaration.name.text !== 'INSTALLATION_OWNED_PROFILE_TUPLES'
+        && declaration.name.text !== 'OPTIONAL_BUNDLES') continue
       if (declaration.initializer === undefined) continue
       if (required.has(declaration.name.text)) found.add(declaration.name.text)
       const before = packages.length
       literals(declaration.initializer, [declaration.name.text])
-      if (packages.length === before) throw new Error(`${PROFILE_SOURCE}: ${declaration.name.text} has no default bundles`)
+      if (declaration.name.text !== 'OPTIONAL_BUNDLES' && packages.length === before) {
+        throw new Error(`${PROFILE_SOURCE}: ${declaration.name.text} has no default bundles`)
+      }
     }
   }
   if (found.size !== required.size) throw new Error(`${PROFILE_SOURCE}: missing default profile declarations`)
   if (webBundles.length === 0) throw new Error(`${PROFILE_SOURCE}: missing default Web bundle list`)
-  return { packages, webBundles }
+  return { packages, webBundles, optionalBundles }
 }
 
 if (import.meta.main) {