@@ -2,11 +2,11 @@
[English ](README.md ) | 中文
文件型[凭据 ](../credentials/README.md ) provider :四层来源,一条诚实的优先级。
文件型[凭据 ](../credentials/README.md )提供方 :四层来源,一条诚实的优先级。
| 层 | 来源 id | 可写 | 优先 |
|---|---|---|---|
| 继承的进程环境 | `env` | 否 | 恒定 优先 |
| 继承的进程环境 | `env` | 否 | 始终 优先 |
| `$DSH_HOME/.credentials.yaml` 文档 | `file` | 是(`set` /`unset` ) | 高于两个 `.env` 层 |
| `<invocation cwd>/.env` | `project-env` | 不在此处 | 高于用户 `.env` |
| `$DSH_HOME/.env` | `user-env` | 不在此处 | 其余情况 |
@@ -35,37 +35,37 @@ DEEPSEEK_API_KEY: sk-…
OPENAI_API_KEY : sk-…
```
该文档只存放凭据,因此任何偏离都是拒绝,而不是跳过某个条目——被静默忽略的键读起来就是「我存进去的密钥没有生效」。非 mapping 的根、非 POSIX 标识符的键、非字符串值、空字符串、重复键以及格式错误的 YAML 全部失败:启动时响亮失败 ,运行期热重载则告警并保留最后可用快照。没有 `version` 字段,也没有包装层;格式就是这个 mapping。
该文档只存放凭据,因此任何偏离都是拒绝,而不是跳过某个条目——被静默忽略的键读起来就是「我存进去的密钥没有生效」。非 mapping 的根、非 POSIX 标识符的键、非字符串值、空字符串、重复键以及格式错误的 YAML 全部失败:启动时明确报错 ,运行期热重载则告警并保留最后可用快照。没有 `version` 字段,也没有包装层;格式就是这个 mapping。
写入是对已解析文档打补丁而不是重建,因此注释与所有未触及条目的排版都会保留。直接位于某条目上方的注释属于该条目的注解,会随它一起删除。每次写入都先在 [`dsh-atomic-write` ](../../util/atomic-write/README.md ) 的跨进程写锁下重读文档、把此前未观察到的一切发布出去,再在仅属主可访问(`0700` )的目录下以 `0600` 权限原子提交——因此并发写入者、或落在 watcher 防抖窗口内的外部编辑会被并入,而不是被覆盖。磁盘上已经无法解析的文档会让写入失败,而不是覆盖 provider 读不懂的内容。
写入是对已解析文档打补丁而不是重建,因此注释与所有未触及条目的排版都会保留。直接位于某条目上方的注释属于该条目的注解,会随它一起删除。每次写入都先在 [`dsh-atomic-write` ](../../util/atomic-write/README.md ) 的跨进程写锁下重读文档、把此前未观察到的一切发布出去,再在仅属主可访问(`0700` )的目录下以 `0600` 权限原子提交——因此并发写入者、或落在 watcher 防抖窗口内的外部编辑会被并入,而不是被覆盖。磁盘上已经无法解析的文档会让写入失败,而不是覆盖提供方 读不懂的内容。
任何字符串值都能往返, 包括多行值, 因此不会再有条目因为缺少可用引号样式而不可写。空的存储值等于不存在( seam 规则)——这也正是文档中的空字符串被直接拒绝的原因:`unset` 删除键,而不是把它置空。
## 权限
provider 以 `0700` 创建目录,以 `0600` 创建或原子替换文档。它对*读取*同样守住这条界线:在 POSIX 上,只要文档带有任何 group 或 other 权限位,就会在解析其内容之前失败——启动时与每次 reload 都检查——并在错误里给出 `chmod 600` 的修复命令。Windows 没有可检查的 mode, 因此在那里跳过该检查而不是伪造它。
提供方 以 `0700` 创建目录,以 `0600` 创建或原子替换文档。它对*读取*同样守住这条界线:在 POSIX 上,只要文档带有任何 group 或 other 权限位,就会在解析其内容之前失败——启动时与每次 reload 都检查——并在错误里给出 `chmod 600` 的修复命令。Windows 没有可检查的 mode, 因此在那里跳过该检查而不是伪造它。
## 热重载
外部编辑在快照**整体替换**后按变更引用逐个发布 `credentials/updated` ——磁盘上删掉的条目绝不在内存滞留。provider 自己的写入按内容识别,只发布属于该次提交的一个事件。运行期文档不可读或无效时保留最后可用快照并告警;文件不存在即空存储;启动时不可读或无效则响亮失败 。
外部编辑在快照**整体替换**后按变更引用逐个发布 `credentials/updated` ——磁盘上删掉的条目绝不在内存滞留。提供方 自己的写入按内容识别,只发布属于该次提交的一个事件。运行期文档不可读或无效时保留最后可用快照并告警;文件不存在即空存储;启动时不可读或无效则明确报错 。
## 安全边界
文档在 `0700` 目录下以 `0600` 权限存放,这挡得住其他 OS 用户,**挡不住**模型。工具进程( bash、文件系统工具) 以同一用户身份运行, 而已交付的 `workspace-write` 文件策略限制的是修改而非读取, 因此它们读这个文件与读该用户拥有的任何其他文件毫无二致; 也没有任何沙箱模式会把它单独挑出来。harness 真正守住的更窄:它绝不把该文档的解析后路径交给模型,也绝不把它载入进程环境——这与用户的普通环境层 `$DSH_HOME/.env` 不同(见 [app-boot 的 Harness home ](../../ui/app-boot/README.md#the-harness-home ))——因此要拿到这个值,需要刻意去读一条并未交给 agent 的路径。
文档在 `0700` 目录下以 `0600` 权限存放,这挡得住其他 OS 用户,**挡不住**模型。工具进程( bash、文件系统工具) 以同一用户身份运行, 而已交付的 `workspace-write` 文件策略限制的是修改而非读取, 因此它们读这个文件与读该用户拥有的任何其他文件毫无二致; 也没有任何沙箱模式会把它单独挑出来。harness 真正守住的更窄:它绝不把该文档的解析后路径交给模型,也绝不把它载入进程环境——这与用户的普通环境层 `$DSH_HOME/.env` 不同(见 [app-boot 的 Harness home ](../../ui/app-boot/README.md#the-harness-home ))——因此要拿到这个值,需要刻意去读一条并未交给 agent(智能体) 的路径。
这是审慎,不是边界。必须让提供方密钥远离自身 agent 的部署无法靠文件权限做到; OS 钥匙串 provider ——一个模型的进程根本读不到的存储——才是延后的答案,它应当作为平级包与本 provider 并列。
这是审慎,不是边界。必须让提供方密钥远离自身 agent 的部署无法靠文件权限做到; OS 钥匙串提供方 ——一个模型的进程根本读不到的存储——才是延后的答案,它应当作为平级包与本提供方 并列。
## Model Experience
## 模型体验
经由消费它的 LLM 适配器间接生效:存储的值为适配器的 提供方请求授权,每个 模型可见面都归 适配器所有 。
经由消费它的 LLM(大语言模型) 适配器间接生效:存储的值为适配器向 提供方发出的 请求授权,所有 模型可见内容均由 适配器负责 。
#### KV Cache effect
#### KV Cache 影响
无直接失效;凭据绝不进入请求前缀。
## Known Limitations and Deferred Work
## 已知限制与暂缓事项
- **同一引用的并发写入是后写胜出**——写锁加读-改-写让并发写入者不会丢掉彼此的条目,但两个写入者编辑同一个引用时仍以较后的写入为准;没有修订检查。
- **同 UID 进程可以读取该文档**——见[安全边界 ](#security-boundary ): 文件效果沙箱模式不会拒绝读取, OS 钥匙串 provider 仍是延后项。
- **同 UID 进程可以读取该文档**——见[安全边界 ](#security-boundary ): 文件效果沙箱模式不会拒绝读取, OS 钥匙串提供方 仍是延后项。
- **环境变化不可见**:快照在启动时冻结,因此启动之后 export 的变量既不会进入解析,也不会进入 `describe` ;要更换来自环境的凭据需要重启。
- **原子但不保证 崩溃持久**——继承自 `dsh-atomic-write` ;存储在启动时重新读取。
- **原子但不具备 崩溃持久性 **——继承自 `dsh-atomic-write` ;存储在启动时重新读取。