feat(cli): restore the home-level user patch layer as $DSH_HOME/cordis.patch.yml

The old $DSH_HOME/config.yaml personal overlay returns under the profile
scheme's filename: machine-local preferences that apply to every profile,
loaded after the profile's own cordis.patch.yml (so the home layer outranks
it) and before --patch overlays and flag patches. Both user layers are
hot-reloaded on long-lived surfaces and shown in --dump-config with their
own provenance labels; the built-bin e2e covers the home layer landing live.
This commit is contained in:
Turtle
2026-08-06 11:02:42 +08:00
parent 0071862d48
commit 65770325e7
12 changed files with 74 additions and 29 deletions

View File

@@ -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/ui/app-boot/README.md
README.md: cb8e254d8157c8ed6cdc0cd8bed1af570265f4ff
README.zh.md: 663c194b7e8d8e678e442455c2984433c16001ad
README.md: 49ad8270ffc62974023cdeba17f3f1356aaf27ae
README.zh.md: 8d01d850d467eb6e21789201fbdef6d79fcc68f0

View File

@@ -37,7 +37,7 @@ A profile is a directory under `$DSH_HOME/profiles/<name>` (the Harness home res
User-level machine-local preferences also live in the Harness home:
- **`.env`** — the credential store of [`dsh-credentials-local`](../../credentials/credentials-local/README.md), read by that provider alone. No surface hoists it into `process.env`: doing so would make every stored key look like a read-only launch override on the next run, blocking rotation from the Web settings page. The environment layers are the ambient one and the invoking directory's `.env` (loaded by the bin; `process.loadEnvFile` never overrides), and a composition without the credential provider keeps resolving keys from those alone.
- **`profiles/<name>/cordis.patch.yml`** — the profile's user patch layer, applied after every bundle layer: an id-targeted patch replaces the named entry's whole `config` (restate unchanged fields), `insert` adds entries, and `!!js` expressions interpolate at mount. A patch naming an entry id absent from the composed tree is a stderr warning. An empty or comments-only file throws (it parses to nothing, not to a list); disable the layer with `[]`.
- **`cordis.patch.yml`** (home level) and **`profiles/<name>/cordis.patch.yml`** — the user patch layers, applied after every bundle layer (per-profile first, then the home-level file, which therefore outranks it): an id-targeted patch replaces the named entry's whole `config` (restate unchanged fields), `insert` adds entries, and `!!js` expressions interpolate at mount. A patch naming an entry id absent from the composed tree is a stderr warning. An empty or comments-only file throws (it parses to nothing, not to a list); disable the layer with `[]`.
Long-lived surfaces keep `cordis.patch.yml` live through `watchPersonalPatches`; one-shot runs read only the startup value. The watcher targets the exact path even when the file or immediate parent does not exist, serializes bursts, and recomposes the user patches inside the caller's layer order (bundle layers below, overlay/flag patches above). A rejected read, parse, or Loader candidate leaves the last good tree running and the HMR service broadcasts `hmr/config-update-failed(filename, Error)` after logging it; observer failures are contained. Disposing the context closes the watcher and drains an active refresh.

View File

@@ -37,7 +37,7 @@ profile 是位于 `$DSH_HOME/profiles/<name>` 下的目录Harness home 由 [`
用户级的机器本地偏好同样位于 Harness home 中:
- **`.env`**[`dsh-credentials-local`](../../credentials/credentials-local/README.md) 的凭据存储,只由该 provider 读取。没有任何表层会把它提升进 `process.env`:那样做会让每个已存密钥在下次运行时看起来都像只读的启动时覆盖,从而阻断从 Web 设置页面轮换密钥。环境层次由环境中的值与调用目录的 `.env` 构成(由 bin 加载;`process.loadEnvFile` 从不覆盖已有值),没有凭据 provider 的组合仍然只从这两者解析密钥。
- **`profiles/<name>/cordis.patch.yml`**profile 的用户 patch 层,应用在所有组合包层之后:按 id 定位的 patch 会替换对应条目的整个 `config`(未改字段也要重述),`insert` 会添加条目,`!!js` 表达式则在挂载时插值。如果 patch 指定的条目 id 不在组合后的树中,则输出一条 stderr 警告。空文件或仅含注释的文件会抛出异常(其解析结果为空,而不是列表);如需禁用该层,请使用 `[]`
- **`cordis.patch.yml`**home 级)与 **`profiles/<name>/cordis.patch.yml`**:用户 patch 层,应用在所有组合包层之后(先应用逐 profile 的文件,再应用 home 级文件,因此后者优先级更高):按 id 定位的 patch 会替换对应条目的整个 `config`(未改字段也要重述),`insert` 会添加条目,`!!js` 表达式则在挂载时插值。如果 patch 指定的条目 id 不在组合后的树中,则输出一条 stderr 警告。空文件或仅含注释的文件会抛出异常(其解析结果为空,而不是列表);如需禁用该层,请使用 `[]`
长期运行的 surface 会持续应用 `cordis.patch.yml` 的变更,具体由 `watchPersonalPatches` 负责一次性运行只读取启动时的值。即使该文件或其直接父目录不存在watcher 仍会监视确切路径;它会串行处理突发变更,并按调用方的层次顺序重新组合用户 patch组合包层在下、overlay标志 patch 在上)。读取失败、解析失败或 Loader 候选被拒时最后一个可用树会继续运行HMR 服务记录错误后广播 `hmr/config-update-failed(filename, Error)`,并隔离 observer 失败。上下文 dispose 时会关闭 watcher并等待进行中的刷新结束。