소스 검색

fix(credentials): name reference update event explicitly

Yichen Jiang 3 주 전
부모
커밋
fecfabcac4
46개의 변경된 파일93개의 추가작업 그리고 93개의 파일을 삭제
  1. 2 2
      .agents/notes/implemented/architecture/2026-07-30-credential-boundaries-and-atomic-registration.i18n.yaml
  2. 2 2
      .agents/notes/implemented/architecture/2026-07-30-credential-boundaries-and-atomic-registration.md
  3. 2 2
      .agents/notes/implemented/architecture/2026-07-30-credential-boundaries-and-atomic-registration.zh.md
  4. 2 2
      .agents/notes/implemented/architecture/2026-08-10-remote-event-delivery.i18n.yaml
  5. 2 2
      .agents/notes/implemented/architecture/2026-08-10-remote-event-delivery.md
  6. 2 2
      .agents/notes/implemented/architecture/2026-08-10-remote-event-delivery.zh.md
  7. 2 2
      docs/event-producer-consumer.i18n.yaml
  8. 1 1
      docs/event-producer-consumer.md
  9. 1 1
      docs/event-producer-consumer.zh.md
  10. 2 2
      docs/subsystems/credentials.i18n.yaml
  11. 7 7
      docs/subsystems/credentials.md
  12. 7 7
      docs/subsystems/credentials.zh.md
  13. 1 1
      packages/api/remotes/src/remote-events.ts
  14. 3 3
      packages/client/runtime/tests/wire-events.client.spec.ts
  15. 2 2
      packages/client/ui-settings-models/README.i18n.yaml
  16. 0 0
      packages/client/ui-settings-models/README.md
  17. 0 0
      packages/client/ui-settings-models/README.zh.md
  18. 1 1
      packages/client/ui-settings-models/src/client/index.ts
  19. 2 2
      packages/client/ui-settings-models/tests/apply.client.spec.ts
  20. 2 2
      packages/client/ui-settings-plugins/README.i18n.yaml
  21. 1 1
      packages/client/ui-settings-plugins/README.md
  22. 1 1
      packages/client/ui-settings-plugins/README.zh.md
  23. 1 1
      packages/client/ui-settings-plugins/src/client/index.ts
  24. 2 2
      packages/client/ui-settings-plugins/tests/apply.client.spec.ts
  25. 2 2
      packages/credentials/credentials-local/README.i18n.yaml
  26. 1 1
      packages/credentials/credentials-local/README.md
  27. 1 1
      packages/credentials/credentials-local/README.zh.md
  28. 1 1
      packages/credentials/credentials-local/src/invariant.ts
  29. 1 1
      packages/credentials/credentials-local/tests/local.spec.ts
  30. 7 7
      packages/credentials/credentials-local/tests/review-fixes.spec.ts
  31. 4 4
      packages/credentials/credentials-local/tests/watcher.spec.ts
  32. 2 2
      packages/credentials/credentials/README.i18n.yaml
  33. 1 1
      packages/credentials/credentials/README.md
  34. 1 1
      packages/credentials/credentials/README.zh.md
  35. 3 3
      packages/credentials/credentials/src/index.ts
  36. 3 3
      packages/credentials/credentials/src/invariant.ts
  37. 3 3
      packages/credentials/credentials/src/types.ts
  38. 2 2
      packages/credentials/credentials/tests/credentials.spec.ts
  39. 1 1
      packages/credentials/credentials/tests/invariant.spec.ts
  40. 2 2
      packages/credentials/credentials/tests/memory.ts
  41. 3 3
      packages/extensions/tool-cordis/src/api-catalog.ts
  42. 2 2
      packages/host/apiproxy/README.i18n.yaml
  43. 0 0
      packages/host/apiproxy/README.md
  44. 0 0
      packages/host/apiproxy/README.zh.md
  45. 4 4
      packages/host/apiproxy/tests/api-proxy-config.spec.ts
  46. 1 1
      packages/test-support/client-runtime/tests/remote.client.spec.ts

+ 2 - 2
.agents/notes/implemented/architecture/2026-07-30-credential-boundaries-and-atomic-registration.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/architecture/2026-07-30-credential-boundaries-and-atomic-registration.md
-2026-07-30-credential-boundaries-and-atomic-registration.md: 69ddfdcf3cd6c15218c672d43cc5aadfd48a0223
-2026-07-30-credential-boundaries-and-atomic-registration.zh.md: d234a073467c743aa90e8e60f1f96f4d288fd651
+2026-07-30-credential-boundaries-and-atomic-registration.md: 95c16ef2bf3d82d7b8b53e3975ece3f0c63402f3
+2026-07-30-credential-boundaries-and-atomic-registration.zh.md: 56ce0d8cbfc0f9023aca406ad43797e7347fdc77

+ 2 - 2
.agents/notes/implemented/architecture/2026-07-30-credential-boundaries-and-atomic-registration.md

@@ -8,7 +8,7 @@ English | [中文](2026-07-30-credential-boundaries-and-atomic-registration.zh.m
 
 ## Problem
 
-The credential path leaked across boundaries it had drawn. The shipped surfaces hoisted `$DSH_HOME/.env` into `process.env` before cordis booted, so on the next run `credentials-local` classified every key it had stored itself as a read-only ambient launch override: `describe()` reported `source: 'env'` with `writable: false`, `set`/`unset` rejected as shadowed, and a key stored from the web page or TUI became unrotatable and undeletable while the adapter kept using the value captured at launch. The store's own write path repeated the settings-file defects the settings write-path note fixes (two independent chains, whole-file render from a stale cache), plus editor bugs of its own: a physical line inside another key's quoted multi-line value read as an assignment, CRLF endings degraded to LF, a multi-line entry reported `writable: true` while `set` always threw, and `credentials/updated` was emitted bare after the commit, so one broken observer made a durable write look failed. On the read side, the file's `0600` mode stops other OS users but not the model, whose bash and filesystem tools run as the same user.
+The credential path leaked across boundaries it had drawn. The shipped surfaces hoisted `$DSH_HOME/.env` into `process.env` before cordis booted, so on the next run `credentials-local` classified every key it had stored itself as a read-only ambient launch override: `describe()` reported `source: 'env'` with `writable: false`, `set`/`unset` rejected as shadowed, and a key stored from the web page or TUI became unrotatable and undeletable while the adapter kept using the value captured at launch. The store's own write path repeated the settings-file defects the settings write-path note fixes (two independent chains, whole-file render from a stale cache), plus editor bugs of its own: a physical line inside another key's quoted multi-line value read as an assignment, CRLF endings degraded to LF, a multi-line entry reported `writable: true` while `set` always threw, and `credentials/reference-updated` was emitted bare after the commit, so one broken observer made a durable write look failed. On the read side, the file's `0600` mode stops other OS users but not the model, whose bash and filesystem tools run as the same user.
 
 Two request-path defects sat beside them. DeepSeek resolved connection and credential facts independently, so a settings generation the resolver rejected could still pair its credential choice with the previous generation's endpoint. pi-ai handed the SDK `undefined` when a configured `apiKeyEnv` resolved to nothing, letting pi-ai's own environment discovery authenticate with an unrelated provider key — another tenant, silently billed. And its route swap disposed the old registration before creating the new one: a route another adapter owned dropped every existing route, after which the facts cache could equal the registry's, so restoring the working configuration never re-applied.
 
@@ -22,7 +22,7 @@ Two request-path defects sat beside them. DeepSeek resolved connection and crede
 
 **Route replacement is a registry operation, not a caller sequence.** `registerAdapter` returns a handle carrying `replace(providers)`: the candidate set is validated in full first (conflicts, names, provider metadata), then swapped in one synchronous section. A refused replacement leaves the previous routes registered and serving, and the caller's facts cache only advances after the registry actually holds the new set, so reverting to a working configuration re-applies. pi-ai's registration facts are sorted by provider, so a settings document that merely reorders its keys is no longer a route change.
 
-**Contained publication for committed credential writes.** `CredentialProvider.notifyUpdated` fans `credentials/updated` out one listener at a time; sync throws and async rejections are logged without changing the committed operation's outcome, and `INVARIANT`-coded failures rethrow after every listener ran — the same shape the settings seam uses for `settings/updated`. `installSettingsSection`'s cleanup now distinguishes its two triggers: a provider detaching still falls back to the composition entry and re-derives, while the consumer's own unload returns immediately instead of re-registering routes during teardown.
+**Contained publication for committed credential writes.** `CredentialProvider.notifyUpdated` fans `credentials/reference-updated` out one listener at a time; sync throws and async rejections are logged without changing the committed operation's outcome, and `INVARIANT`-coded failures rethrow after every listener ran — the same shape the settings seam uses for `settings/updated`. `installSettingsSection`'s cleanup now distinguishes its two triggers: a provider detaching still falls back to the composition entry and re-derives, while the consumer's own unload returns immediately instead of re-registering routes during teardown.
 
 ## Alternatives considered
 

+ 2 - 2
.agents/notes/implemented/architecture/2026-07-30-credential-boundaries-and-atomic-registration.zh.md

@@ -10,7 +10,7 @@ Status: implemented
 
 凭据路径越过它自己划下的边界发生了泄漏。已交付的各个面在 Cordis 启动之前就把 `$DSH_HOME/.env` 提升进了 `process.env`,于是下一次运行时,`credentials-local` 会把它自己存下的每个键都判成来自环境的只读启动覆盖:`describe()` 报告 `source: 'env'` 且 `writable: false`,`set`/`unset` 以被遮蔽为由拒绝,从 web 页面或 TUI 存入的密钥既无法轮换也无法删除,而适配器还在继续使用启动时捕获的那个值。
 
-存储自身的写路径重演了 settings 写路径 note 在 settings-file 修掉的那些缺陷(两条相互独立的链、从陈旧缓存渲染整份文件),还叠加了编辑器自己的缺陷:另一个键的带引号多行值内部的一条物理行会被读成赋值,CRLF 行尾会退化成 LF,多行条目报告 `writable: true` 而 `set` 总是抛错,`credentials/updated` 又在提交之后裸发,于是一个出错的观察者就能让一次已经落盘的写入看起来失败。
+存储自身的写路径重演了 settings 写路径 note 在 settings-file 修掉的那些缺陷(两条相互独立的链、从陈旧缓存渲染整份文件),还叠加了编辑器自己的缺陷:另一个键的带引号多行值内部的一条物理行会被读成赋值,CRLF 行尾会退化成 LF,多行条目报告 `writable: true` 而 `set` 总是抛错,`credentials/reference-updated` 又在提交之后裸发,于是一个出错的观察者就能让一次已经落盘的写入看起来失败。
 
 在读取一侧,文件的 `0600` 权限挡得住其他 OS 用户,却挡不住模型:它的 bash 与文件系统工具就以同一个用户身份运行。
 
@@ -26,7 +26,7 @@ Status: implemented
 
 **路由替换是注册表的操作,不是调用方的一串步骤。**`registerAdapter` 返回一个携带 `replace(providers)` 的句柄:候选集合先被完整校验(冲突、名称、提供方元数据),再在一个同步区段内完成替换。被拒绝的替换会让先前的路由保持注册并继续服务,而调用方的事实缓存只有在注册表确实持有新集合之后才会推进,因此改回可用配置时会重新生效。pi-ai 的注册事实按提供方排序,因此仅仅调换键顺序的设置文档不再算作路由变更。
 
-**已提交的凭据写入采用收容式发布。**`CredentialProvider.notifyUpdated` 逐个监听器扇出 `credentials/updated`;同步抛错与异步 rejection 都只记日志,不改变已提交操作的结果,而带 `INVARIANT` 代码的失败会在每个监听器都运行完之后重抛——与 settings seam 处理 `settings/updated` 的形状相同。`installSettingsSection` 的清理现在会区分它的两个触发来源:提供方脱离时仍回退到组合的 entry 配置并重新推导,而消费方自身卸载时立即返回,不再在拆卸过程中重新注册路由。
+**已提交的凭据写入采用收容式发布。**`CredentialProvider.notifyUpdated` 逐个监听器扇出 `credentials/reference-updated`;同步抛错与异步 rejection 都只记日志,不改变已提交操作的结果,而带 `INVARIANT` 代码的失败会在每个监听器都运行完之后重抛——与 settings seam 处理 `settings/updated` 的形状相同。`installSettingsSection` 的清理现在会区分它的两个触发来源:提供方脱离时仍回退到组合的 entry 配置并重新推导,而消费方自身卸载时立即返回,不再在拆卸过程中重新注册路由。
 
 ## 曾考虑的替代方案
 

+ 2 - 2
.agents/notes/implemented/architecture/2026-08-10-remote-event-delivery.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/architecture/2026-08-10-remote-event-delivery.md
-2026-08-10-remote-event-delivery.md: 9c2b5087772a5a343514d1766a14e90edb261813
-2026-08-10-remote-event-delivery.zh.md: 213715f5d9efcc11290059e5c5b0c06bbd7e255d
+2026-08-10-remote-event-delivery.md: ee3d9884b53f5fa5d0b0072660888c5f4d283b1b
+2026-08-10-remote-event-delivery.zh.md: a51a0ade174e70177f438a4bbc6bcd452807f436

+ 2 - 2
.agents/notes/implemented/architecture/2026-08-10-remote-event-delivery.md

@@ -8,7 +8,7 @@ English | [中文](2026-08-10-remote-event-delivery.zh.md)
 
 [Typert Gateway targeted method calls](../../implemented/architecture/2026-08-02-typert-remote-method-calls.md) cover only the request/response shape and deliberately leave Session event streams and stateful interactions to separate designs. Every **one-way Host-to-consumer push** therefore still rides the legacy API Proxy.
 
-The Host owns a family of one-way events whose payloads are already JSON and whose emission never binds an AgentScope: `agent-preset/selected`, `commands/change`, `credentials/updated`, `llm/adapters-updated`, and `settings/document-updated`. Reaching one UI subscriber took four hops: the Host cordis event, a hand-written `HostFrame` variant plus its zod branch in apiproxy, a hand-written bridge in client/runtime that re-emitted it as a Client cordis event, and finally the consumer's `ctx.on(...)`. Adding one such event edited five places (frame union, zod union, host-stream listener, client bridge, a duplicated Client-side `Events` declaration), and not one of them stated a new fact: the name, the payload type, and the emission point were all declared by the owner package's cordis `Events` merge.
+The Host owns a family of one-way events whose payloads are already JSON and whose emission never binds an AgentScope: `agent-preset/selected`, `commands/change`, `credentials/reference-updated`, `llm/adapters-updated`, and `settings/document-updated`. Reaching one UI subscriber took four hops: the Host cordis event, a hand-written `HostFrame` variant plus its zod branch in apiproxy, a hand-written bridge in client/runtime that re-emitted it as a Client cordis event, and finally the consumer's `ctx.on(...)`. Adding one such event edited five places (frame union, zod union, host-stream listener, client bridge, a duplicated Client-side `Events` declaration), and not one of them stated a new fact: the name, the payload type, and the emission point were all declared by the owner package's cordis `Events` merge.
 
 That duplicated declaration is also **lossy**: the Client side restates it as `settings/changed(ns: string)`, flattening a branded type into bare `string` — the opposite of the Remote method contract, where a consumer type points at the business package's one canonical symbol.
 
@@ -75,7 +75,7 @@ Delivery shares no implementation with the cordis event system: one-way only, no
 export const API_REMOTE_FORWARDED_EVENTS = [
   'agent-preset/selected',
   'commands/change',
-  'credentials/updated',
+  'credentials/reference-updated',
   'llm/adapters-updated',
   'settings/document-updated',
 ] as const

+ 2 - 2
.agents/notes/implemented/architecture/2026-08-10-remote-event-delivery.zh.md

@@ -8,7 +8,7 @@ Status: implemented
 
 [Typert Remote 方法调用](../../implemented/architecture/2026-08-02-typert-remote-method-calls.md)只覆盖「一次请求一个结果」的定向调用,明确把 Session 事件流与有状态交互留在别处;Host 向消费端的**单向事件推送**因此仍然全部压在遗留的 API Proxy 上。
 
-Host 拥有 `agent-preset/selected`、`commands/change`、`credentials/updated`、`llm/adapters-updated`、`settings/document-updated` 这五条单向事件;它们既不依赖 AgentScope,载荷也本来就是 JSON。过去每条都要穿过 host cordis 事件、apiproxy 手写帧、client/runtime 手写桥和 Client 事件别名才能抵达 UI,而这些层没有陈述 owner 事件之外的新事实。
+Host 拥有 `agent-preset/selected`、`commands/change`、`credentials/reference-updated`、`llm/adapters-updated`、`settings/document-updated` 这五条单向事件;它们既不依赖 AgentScope,载荷也本来就是 JSON。过去每条都要穿过 host cordis 事件、apiproxy 手写帧、client/runtime 手写桥和 Client 事件别名才能抵达 UI,而这些层没有陈述 owner 事件之外的新事实。
 
 那份重复声明还是**有损**的:client 侧写成 `settings/changed(ns: string)`,brand 类型在这一跳被拍平成裸 `string`,与 Remote 方法侧「消费端类型指向业务包唯一符号」的既有契约相反。
 
@@ -75,7 +75,7 @@ $dispatch(event: string, args: readonly unknown[]): void
 export const API_REMOTE_FORWARDED_EVENTS = [
   'agent-preset/selected',
   'commands/change',
-  'credentials/updated',
+  'credentials/reference-updated',
   'llm/adapters-updated',
   'settings/document-updated',
 ] as const

+ 2 - 2
docs/event-producer-consumer.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 docs/event-producer-consumer.md
-event-producer-consumer.md: 49c7a128fcf2539d342ad263bb856c40f2a33f0c
-event-producer-consumer.zh.md: dec8b443aa73ae919ceee7cd282846bea4e8ce18
+event-producer-consumer.md: e507e0e83fa32618393d06bfaa746a3e0b1e4611
+event-producer-consumer.zh.md: 94da41dbe23a870db959f5f878d025158efd2858

+ 1 - 1
docs/event-producer-consumer.md

@@ -31,7 +31,7 @@ This matrix shows which packages dispatch each harness-owned event and which pac
 | `cordis/request-run` | `emit` | [`packages/extensions/cordis-host-runner/src/types.ts:367`](../packages/extensions/cordis-host-runner/src/types.ts) | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) (`emit`) | `apiproxy` |
 | `cordis/request-run-resolved` | `emit` | [`packages/extensions/cordis-host-runner/src/types.ts:373`](../packages/extensions/cordis-host-runner/src/types.ts) | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) (`emit`) | `apiproxy` |
 | `credentials/record-updated` | `emit` | [`packages/credentials/credentials/src/types.ts:87`](../packages/credentials/credentials/src/types.ts) | [`credentials`](../packages/credentials/credentials) (`events.dispatch`) | [`authorization`](../packages/credentials/authorization) |
-| `credentials/updated` | `emit` | [`packages/credentials/credentials/src/types.ts:75`](../packages/credentials/credentials/src/types.ts) | [`credentials`](../packages/credentials/credentials) (`events.dispatch`) | `apiproxy`, [`credentials`](../packages/credentials/credentials) |
+| `credentials/reference-updated` | `emit` | [`packages/credentials/credentials/src/types.ts:75`](../packages/credentials/credentials/src/types.ts) | [`credentials`](../packages/credentials/credentials) (`events.dispatch`) | `apiproxy`, [`credentials`](../packages/credentials/credentials) |
 | `domain/changed` | `emit` | [`packages/storage/storage-domain/src/events.ts:46`](../packages/storage/storage-domain/src/events.ts) | [`storage-domain`](../packages/storage/storage-domain) (`emit`) | `apiproxy`, [`storage-domain`](../packages/storage/storage-domain), [`workspace`](../packages/workspace/workspace) |
 | `fs/edit-intent` | `waterfall` | [`packages/fs/fs/src/index.ts:66`](../packages/fs/fs/src/index.ts) | [`tool-fs`](../packages/fs/tool-fs) (`waterfall`), [`tool-str-replace-editor`](../packages/fs/tool-str-replace-editor) (`waterfall`) | [`fs-observation-policy`](../packages/fs/fs-observation-policy) |
 | `fs/observed` | `emit` | [`packages/fs/fs/src/index.ts:76`](../packages/fs/fs/src/index.ts) | [`tool-fs`](../packages/fs/tool-fs) (`emit`), [`tool-str-replace-editor`](../packages/fs/tool-str-replace-editor) (`emit`) | [`fs-observation-policy`](../packages/fs/fs-observation-policy), [`skill-filesystem`](../packages/skill/skill-filesystem) |

+ 1 - 1
docs/event-producer-consumer.zh.md

@@ -33,7 +33,7 @@
 | `cordis/request-run` | `emit` | [`packages/extensions/cordis-host-runner/src/types.ts:367`](../packages/extensions/cordis-host-runner/src/types.ts) | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) (`emit`) | `apiproxy` |
 | `cordis/request-run-resolved` | `emit` | [`packages/extensions/cordis-host-runner/src/types.ts:373`](../packages/extensions/cordis-host-runner/src/types.ts) | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) (`emit`) | `apiproxy` |
 | `credentials/record-updated` | `emit` | [`packages/credentials/credentials/src/types.ts:87`](../packages/credentials/credentials/src/types.ts) | [`credentials`](../packages/credentials/credentials) (`events.dispatch`) | [`authorization`](../packages/credentials/authorization) |
-| `credentials/updated` | `emit` | [`packages/credentials/credentials/src/types.ts:75`](../packages/credentials/credentials/src/types.ts) | [`credentials`](../packages/credentials/credentials) (`events.dispatch`) | `apiproxy`, [`credentials`](../packages/credentials/credentials) |
+| `credentials/reference-updated` | `emit` | [`packages/credentials/credentials/src/types.ts:75`](../packages/credentials/credentials/src/types.ts) | [`credentials`](../packages/credentials/credentials) (`events.dispatch`) | `apiproxy`, [`credentials`](../packages/credentials/credentials) |
 | `domain/changed` | `emit` | [`packages/storage/storage-domain/src/events.ts:46`](../packages/storage/storage-domain/src/events.ts) | [`storage-domain`](../packages/storage/storage-domain) (`emit`) | `apiproxy`, [`storage-domain`](../packages/storage/storage-domain), [`workspace`](../packages/workspace/workspace) |
 | `fs/edit-intent` | `waterfall` | [`packages/fs/fs/src/index.ts:66`](../packages/fs/fs/src/index.ts) | [`tool-fs`](../packages/fs/tool-fs) (`waterfall`), [`tool-str-replace-editor`](../packages/fs/tool-str-replace-editor) (`waterfall`) | [`fs-observation-policy`](../packages/fs/fs-observation-policy) |
 | `fs/observed` | `emit` | [`packages/fs/fs/src/index.ts:76`](../packages/fs/fs/src/index.ts) | [`tool-fs`](../packages/fs/tool-fs) (`emit`), [`tool-str-replace-editor`](../packages/fs/tool-str-replace-editor) (`emit`) | [`fs-observation-policy`](../packages/fs/fs-observation-policy), [`skill-filesystem`](../packages/skill/skill-filesystem) |

+ 2 - 2
docs/subsystems/credentials.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 docs/subsystems/credentials.md
-credentials.md: 8d75a254109dcce5d0b54cd4fd293a8ca4e335c5
-credentials.zh.md: 46edda84a063c539748da344d7efa66c7af666e7
+credentials.md: 459511965d796cd34d1a817110016bcb922d2988
+credentials.zh.md: d833f7897008dd9f9c062d8fbefc1e4ba9c0d959

+ 7 - 7
docs/subsystems/credentials.md

@@ -47,7 +47,7 @@ interface CredentialInfo {
 
 ## Change commits
 
-`credentials/updated (ref)` fires after a committed change to a provider-managed source — a `set`, an `unset`, or an external edit observed in storage. Ambient process-environment changes are not observable and never emit. Consumers do not need the event (they re-resolve per operation); it exists for configuration surfaces refreshing a "configured" badge.
+`credentials/reference-updated (ref)` fires after a committed change to a provider-managed source — a `set`, an `unset`, or an external edit observed in storage. Ambient process-environment changes are not observable and never emit. Consumers do not need the event (they re-resolve per operation); it exists for configuration surfaces refreshing a "configured" badge.
 
 <!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
 
@@ -244,16 +244,16 @@ Source: [`packages/credentials/authorization/src/index.ts:57`](../../packages/cr
 
 #### `credentials/record-updated` — emit
 
-Committed change to a stored credential record: a `modifyRecord` that wrote, a `deleteRecord` that removed, or an external edit observed in storage. Separate from `credentials/updated` because the two key grammars are disjoint — a listener that received both on one event could not tell which space a subject belongs to. Listener failures are contained on the same terms as `credentials/updated`.
+Committed change to a stored credential record: a `modifyRecord` that wrote, a `deleteRecord` that removed, or an external edit observed in storage. Separate from `credentials/reference-updated` because the two key grammars are disjoint — a listener that received both on one event could not tell which space a subject belongs to. Listener failures are contained on the same terms as `credentials/reference-updated`.
 
 ```ts cordis-catalog
 /**
  * Committed change to a stored credential record: a `modifyRecord` that
  * wrote, a `deleteRecord` that removed, or an external edit observed in
- * storage. Separate from `credentials/updated` because the two key
+ * storage. Separate from `credentials/reference-updated` because the two key
  * grammars are disjoint — a listener that received both on one event could
  * not tell which space a subject belongs to. Listener failures are
- * contained on the same terms as `credentials/updated`.
+ * contained on the same terms as `credentials/reference-updated`.
  * @param key - the record whose stored value changed.
  * @mode emit
  */
@@ -262,9 +262,9 @@ Committed change to a stored credential record: a `modifyRecord` that wrote, a `
 
 Source: [`packages/credentials/credentials/src/types.ts:87`](../../packages/credentials/credentials/src/types.ts)
 
-<a id="credentialsupdated--emit"></a>
+<a id="credentialsreference-updated--emit"></a>
 
-#### `credentials/updated` — emit
+#### `credentials/reference-updated` — emit
 
 Committed change to a provider-managed credential source: a `set`, an `unset`, or an external edit observed in storage. Ambient process-environment changes are not observable and never emit. Listener failures are contained and logged — a sync throw and an async rejection alike — without changing the committed operation's outcome, except `INVARIANT`-coded failures, which rethrow after every listener ran; that rethrow reaches the emitter only from synchronous listeners, so invariant checks on this event must not be async functions.
 
@@ -281,7 +281,7 @@ Committed change to a provider-managed credential source: a `set`, an `unset`, o
  * @param ref - the reference whose stored value changed.
  * @mode emit
  */
-'credentials/updated'(ref: CredentialRef): void
+'credentials/reference-updated'(ref: CredentialRef): void
 ```
 
 Source: [`packages/credentials/credentials/src/types.ts:75`](../../packages/credentials/credentials/src/types.ts)

+ 7 - 7
docs/subsystems/credentials.zh.md

@@ -47,7 +47,7 @@ interface CredentialInfo {
 
 ## 已提交的变更
 
-`credentials/updated (ref)` 在提供方管理的来源发生已提交变更后发出——`set`、`unset` 或在存储中观察到的外部编辑。进程环境自身的变化不可观测,永不发出事件。消费方不需要该事件(它们按操作重新解析);它服务于配置界面刷新「已配置」徽标。
+`credentials/reference-updated (ref)` 在提供方管理的来源发生已提交变更后发出——`set`、`unset` 或在存储中观察到的外部编辑。进程环境自身的变化不可观测,永不发出事件。消费方不需要该事件(它们按操作重新解析);它服务于配置界面刷新「已配置」徽标。
 
 <!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
 
@@ -244,16 +244,16 @@ Source: [`packages/credentials/authorization/src/index.ts:57`](../../packages/cr
 
 #### `credentials/record-updated` — emit
 
-Committed change to a stored credential record: a `modifyRecord` that wrote, a `deleteRecord` that removed, or an external edit observed in storage. Separate from `credentials/updated` because the two key grammars are disjoint — a listener that received both on one event could not tell which space a subject belongs to. Listener failures are contained on the same terms as `credentials/updated`.
+Committed change to a stored credential record: a `modifyRecord` that wrote, a `deleteRecord` that removed, or an external edit observed in storage. Separate from `credentials/reference-updated` because the two key grammars are disjoint — a listener that received both on one event could not tell which space a subject belongs to. Listener failures are contained on the same terms as `credentials/reference-updated`.
 
 ```ts cordis-catalog
 /**
  * Committed change to a stored credential record: a `modifyRecord` that
  * wrote, a `deleteRecord` that removed, or an external edit observed in
- * storage. Separate from `credentials/updated` because the two key
+ * storage. Separate from `credentials/reference-updated` because the two key
  * grammars are disjoint — a listener that received both on one event could
  * not tell which space a subject belongs to. Listener failures are
- * contained on the same terms as `credentials/updated`.
+ * contained on the same terms as `credentials/reference-updated`.
  * @param key - the record whose stored value changed.
  * @mode emit
  */
@@ -262,9 +262,9 @@ Committed change to a stored credential record: a `modifyRecord` that wrote, a `
 
 Source: [`packages/credentials/credentials/src/types.ts:87`](../../packages/credentials/credentials/src/types.ts)
 
-<a id="credentialsupdated--emit"></a>
+<a id="credentialsreference-updated--emit"></a>
 
-#### `credentials/updated` — emit
+#### `credentials/reference-updated` — emit
 
 Committed change to a provider-managed credential source: a `set`, an `unset`, or an external edit observed in storage. Ambient process-environment changes are not observable and never emit. Listener failures are contained and logged — a sync throw and an async rejection alike — without changing the committed operation's outcome, except `INVARIANT`-coded failures, which rethrow after every listener ran; that rethrow reaches the emitter only from synchronous listeners, so invariant checks on this event must not be async functions.
 
@@ -281,7 +281,7 @@ Committed change to a provider-managed credential source: a `set`, an `unset`, o
  * @param ref - the reference whose stored value changed.
  * @mode emit
  */
-'credentials/updated'(ref: CredentialRef): void
+'credentials/reference-updated'(ref: CredentialRef): void
 ```
 
 Source: [`packages/credentials/credentials/src/types.ts:75`](../../packages/credentials/credentials/src/types.ts)

+ 1 - 1
packages/api/remotes/src/remote-events.ts

@@ -17,7 +17,7 @@
 export const API_REMOTE_FORWARDED_EVENTS = [
   'agent-preset/selected',
   'commands/change',
-  'credentials/updated',
+  'credentials/reference-updated',
   'cordis/request-run',
   'cordis/request-run-resolved',
   'cordis/dynamic-package',

+ 3 - 3
packages/client/runtime/tests/wire-events.client.spec.ts

@@ -28,7 +28,7 @@ function forwardedEventContracts(ctx: Context): void {
     const bare: typeof namespace = 'plain-string'
     void bare; void namespace; void source
   })
-  ctx.remote.$on('credentials/updated', () => {})
+  ctx.remote.$on('credentials/reference-updated', () => {})
   ctx.remote.$on('commands/change', () => {})
   ctx.remote.$on('llm/adapters-updated', () => {})
   ctx.remote.$on('agent-preset/selected', (sessionId, agentPreset) => {
@@ -106,7 +106,7 @@ describe('wire event bridge', () => {
     })
     bench.sinks?.onHostEnvelope?.({
       rpcId: 'r4' as never,
-      payload: { type: 'host/remote-event', event: 'credentials/updated', args: ['OPENAI_API_KEY'] },
+      payload: { type: 'host/remote-event', event: 'credentials/reference-updated', args: ['OPENAI_API_KEY'] },
     })
     // The carrier does not second-guess the name: selecting what a consumer can
     // receive is the allowlist's job, and dropping an unsubscribed name is the
@@ -118,7 +118,7 @@ describe('wire event bridge', () => {
 
     expect(seen).toEqual([
       ['settings/document-updated', 'llm-pi-ai', 7],
-      ['credentials/updated', 'OPENAI_API_KEY'],
+      ['credentials/reference-updated', 'OPENAI_API_KEY'],
       ['nobody/listening', 'ignored'],
     ])
   })

+ 2 - 2
packages/client/ui-settings-models/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/client/ui-settings-models/README.md
-README.md: 0d76d5f31585721ad8f5cd76dd223d72353fdd0c
-README.zh.md: 8ac336245c9d82420c10132b3b935cf00f5ea889
+README.md: 781d7a8134c8118ddf4a78f3ee153a8043bae0a6
+README.zh.md: a652e9a250d855c0e91f842ec2332f4a49f17856

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 0 - 0
packages/client/ui-settings-models/README.md


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 0 - 0
packages/client/ui-settings-models/README.zh.md


+ 1 - 1
packages/client/ui-settings-models/src/client/index.ts

@@ -108,7 +108,7 @@ export function apply(ctx: ClientContext): void {
     const refreshModels = (): void => { refreshIfLoaded(controller) }
     const disposers = [
       ctx.remote.$on('settings/document-updated', () => { refreshModels() }),
-      ctx.remote.$on('credentials/updated', refreshModels),
+      ctx.remote.$on('credentials/reference-updated', refreshModels),
       ctx.remote.$on('llm/adapters-updated', refreshModels),
       ctx.on('connection/reset', refreshModels),
     ]

+ 2 - 2
packages/client/ui-settings-models/tests/apply.client.spec.ts

@@ -177,7 +177,7 @@ describe('pushed invalidations', () => {
     await b.ctx.plugin({ inject: [...inject], apply }).await()
     // The fake wire face has no methods: a fetch attempt would throw.
     b.ctx.remote.$dispatch('settings/document-updated', ['llm-pi-ai', 1])
-    b.ctx.remote.$dispatch('credentials/updated', ['OPENAI_API_KEY'])
+    b.ctx.remote.$dispatch('credentials/reference-updated', ['OPENAI_API_KEY'])
     b.ctx.remote.$dispatch('llm/adapters-updated', [])
     b.ctx.emit('connection/reset')
   })
@@ -210,7 +210,7 @@ describe('pushed invalidations', () => {
     )()
     injected.controller.store.update((state) => { state.status = 'ready' })
     const load = vi.spyOn(injected.controller, 'load').mockResolvedValue()
-    b.ctx.remote.$dispatch('credentials/updated', ['DEEPSEEK_API_KEY'])
+    b.ctx.remote.$dispatch('credentials/reference-updated', ['DEEPSEEK_API_KEY'])
     expect(load).toHaveBeenCalledTimes(1)
   })
 

+ 2 - 2
packages/client/ui-settings-plugins/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/client/ui-settings-plugins/README.md
-README.md: 92ba426b6bee3ae73e184dfcfd418e889bd47086
-README.zh.md: dc319ea9f128016b6df821575cb4d75740f04579
+README.md: 0ee0edbebadad8e8e895002cd9eb60a20f45809d
+README.zh.md: 75cc63ccf5280bba1b94bcf8930b9ee9432440e5

+ 1 - 1
packages/client/ui-settings-plugins/README.md

@@ -20,7 +20,7 @@ A card stages what the user types and writes it only when they save. Each contro
 
 Saving writes each staged field through the client settings scope, which fences every write with the namespace revision it read, so a form that has drifted from the document is refused rather than overwriting a concurrent change. The Host is the only authority on whether a value was accepted — its validators own the constraints no schema can express — so the card reads the section back afterwards and reports a save that did not land, keeping those drafts for the user to correct.
 
-A key can also be written from another surface — the Models page addresses the same reference — which changes no settings section, so the card re-reads on the forwarded `credentials/updated` event for the reference it watches.
+A key can also be written from another surface — the Models page addresses the same reference — which changes no settings section, so the card re-reads on the forwarded `credentials/reference-updated` event for the reference it watches.
 
 A field's presence in the raw user layer — not its value — is what marks it overridden; a reset clears that field so it re-inherits the composition layer. Secret-role fields never ride a response, so a key control starts blank, reports only whether one is configured, and writes through the credentials domain rather than the settings section; a blank draft writes nothing and keeps the stored key.
 

+ 1 - 1
packages/client/ui-settings-plugins/README.zh.md

@@ -20,7 +20,7 @@
 
 保存时,每个暂存字段都通过客户端 settings scope 写入,该 scope 用读取时的命名空间 revision 为每次写入设栅,因此已与文档脱节的表单会被拒绝,而不是覆盖并发变更。某个值是否被接受只有 Host 说了算——schema 表达不了的约束归它的校验器所有——因此卡片在写入后回读分节,报告没有落盘的保存,并保留这些草稿供用户修改。
 
-密钥也可能从别的表层写入——模型页寻址的是同一个引用——而那不改变任何 settings 分节,因此卡片会在转发来的 `credentials/updated` 事件报告它所关注的引用时重读。
+密钥也可能从别的表层写入——模型页寻址的是同一个引用——而那不改变任何 settings 分节,因此卡片会在转发来的 `credentials/reference-updated` 事件报告它所关注的引用时重读。
 
 字段是否被覆盖,取决于它是否出现在原始用户层中,而非取决于它的值;重置会清除该字段,使其重新继承组装层。secret 角色的字段绝不搭乘响应,因此密钥控件初始为空、只报告是否已配置,并经由 credentials 领域而非 settings 分节写入;空草稿不写入任何东西,保留已存密钥。
 

+ 1 - 1
packages/client/ui-settings-plugins/src/client/index.ts

@@ -68,7 +68,7 @@ export function apply(ctx: ClientContext): void {
   // scope publishes nothing when one is written. This is the only signal that
   // a key written on another surface reached the Host.
   ctx.effect(
-    () => ctx.remote.$on('credentials/updated', (ref) => { webSearch.refreshCredential(ref) }),
+    () => ctx.remote.$on('credentials/reference-updated', (ref) => { webSearch.refreshCredential(ref) }),
     'ui-settings-plugins: credential invalidations',
   )
 

+ 2 - 2
packages/client/ui-settings-plugins/tests/apply.client.spec.ts

@@ -180,7 +180,7 @@ describe('ui-settings-plugins apply', () => {
 
     // A key written on another surface changes no settings section, so this
     // event is the only thing that reaches the card.
-    ctx.remote.$dispatch('credentials/updated', ['DEEPSEEK_API_KEY'])
+    ctx.remote.$dispatch('credentials/reference-updated', ['DEEPSEEK_API_KEY'])
 
     await vi.waitFor(() => { expect(describeCredentials).toHaveBeenCalledTimes(1) })
   })
@@ -192,7 +192,7 @@ describe('ui-settings-plugins apply', () => {
     await vi.waitFor(() => { expect(describeCredentials).toHaveBeenCalled() })
     describeCredentials.mockClear()
 
-    ctx.remote.$dispatch('credentials/updated', ['SOME_OTHER_KEY'])
+    ctx.remote.$dispatch('credentials/reference-updated', ['SOME_OTHER_KEY'])
     await Promise.resolve()
 
     expect(describeCredentials).not.toHaveBeenCalled()

+ 2 - 2
packages/credentials/credentials-local/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/credentials/credentials-local/README.md
-README.md: cc9c63bd689f3e5dd028add77fb72ca9eb541623
-README.zh.md: 2368d5ff7db3c515e3826d53efad2c35768328f0
+README.md: 1b8e4e5d89dc0eb5da2b04b6281c70316262c03b
+README.zh.md: 7ba7726e46a6cd5c6b26a2e55e001a750a0e6dd1

+ 1 - 1
packages/credentials/credentials-local/README.md

@@ -69,7 +69,7 @@ The provider creates the directory `0700` and creates or atomically replaces the
 
 ## Hot reload
 
-External edits publish `credentials/updated` per changed reference after the snapshot is replaced **wholesale** — an entry deleted on disk never lingers in memory. Before Chokidar opens the target, the provider realpaths its deepest existing ancestor and restores any missing suffix; file access and diagnostics retain the configured path, while Windows cannot mix an 8.3 alias with long-form libuv events. The provider's own writes are recognized by content and publish exactly their one commit event. An unreadable or invalid document at runtime keeps the last good snapshot and warns; an absent file is an empty store; an unreadable or invalid file at boot fails loud.
+External edits publish `credentials/reference-updated` per changed reference after the snapshot is replaced **wholesale** — an entry deleted on disk never lingers in memory. Before Chokidar opens the target, the provider realpaths its deepest existing ancestor and restores any missing suffix; file access and diagnostics retain the configured path, while Windows cannot mix an 8.3 alias with long-form libuv events. The provider's own writes are recognized by content and publish exactly their one commit event. An unreadable or invalid document at runtime keeps the last good snapshot and warns; an absent file is an empty store; an unreadable or invalid file at boot fails loud.
 
 ## Security boundary
 

+ 1 - 1
packages/credentials/credentials-local/README.zh.md

@@ -69,7 +69,7 @@ records:
 
 ## 热重载
 
-外部编辑在快照**整体替换**后按变更引用逐个发布 `credentials/updated`——磁盘上删掉的条目绝不在内存滞留。在 Chokidar 打开目标之前,提供方会对层级最深的现有祖先路径执行 realpath 解析,再拼回缺失的后缀;文件访问和诊断仍使用配置路径,从而避免 Windows 混用 8.3 别名与 libuv 的长格式事件路径。提供方自己的写入按内容识别,只发布属于该次提交的一个事件。运行期文档不可读或无效时保留最后可用快照并告警;文件不存在即空存储;启动时不可读或无效则明确报错。
+外部编辑在快照**整体替换**后按变更引用逐个发布 `credentials/reference-updated`——磁盘上删掉的条目绝不在内存滞留。在 Chokidar 打开目标之前,提供方会对层级最深的现有祖先路径执行 realpath 解析,再拼回缺失的后缀;文件访问和诊断仍使用配置路径,从而避免 Windows 混用 8.3 别名与 libuv 的长格式事件路径。提供方自己的写入按内容识别,只发布属于该次提交的一个事件。运行期文档不可读或无效时保留最后可用快照并告警;文件不存在即空存储;启动时不可读或无效则明确报错。
 
 <a id="security-boundary"></a>
 

+ 1 - 1
packages/credentials/credentials-local/src/invariant.ts

@@ -16,7 +16,7 @@ export const inject = ['invariants']
 
 /**
  * No runtime invariant: the Service Definition companion (`dsh-credentials/invariant`) owns the
- * `credentials/updated` lifecycle contract; this provider's file/environment layering is
+ * `credentials/reference-updated` lifecycle contract; this provider's file/environment layering is
  * asynchronous I/O pinned by its unit suite.
  */
 const install: InvariantInstaller = () => {}

+ 1 - 1
packages/credentials/credentials-local/tests/local.spec.ts

@@ -41,7 +41,7 @@ async function boot(config: ConstructorParameters<typeof LocalCredentialProvider
 
 function updates(ctx: Context): CredentialRef[] {
   const seen: CredentialRef[] = []
-  ctx.on('credentials/updated', (ref) => {
+  ctx.on('credentials/reference-updated', (ref) => {
     seen.push(ref)
   })
   return seen

+ 7 - 7
packages/credentials/credentials-local/tests/review-fixes.spec.ts

@@ -1,5 +1,5 @@
 // Third-review behaviors: read-modify-write under the writer lock (external
-// edits survive an API write), the contained credentials/updated fan-out (a
+// edits survive an API write), the contained credentials/reference-updated fan-out (a
 // broken observer never fails a committed write), and the YAML document
 // editor's isolation between entries.
 import { afterEach, describe, expect, it, vi } from 'vitest'
@@ -45,7 +45,7 @@ describe('read-modify-write', () => {
     const path = join(dir, '.credentials.yaml')
     const ctx = await boot({ path, watch: false })
     const seen: string[] = []
-    ctx.on('credentials/updated', (ref) => { seen.push(ref) })
+    ctx.on('credentials/reference-updated', (ref) => { seen.push(ref) })
     await ctx.credentials.set(ALPHA, 'one')
     // The external edit has landed on disk but no watcher reported it (watch
     // is off — the same blind spot as a debounce window or a missed event).
@@ -117,11 +117,11 @@ describe('contained update fan-out', () => {
   it('does not fail a committed set when a listener throws, and later listeners still run', async () => {
     const dir = await tempDir()
     const ctx = await boot({ path: join(dir, '.credentials.yaml'), watch: false })
-    ctx.on('credentials/updated', () => {
+    ctx.on('credentials/reference-updated', () => {
       throw new Error('observer boom')
     })
     const second = vi.fn()
-    ctx.on('credentials/updated', second)
+    ctx.on('credentials/reference-updated', second)
     await expect(ctx.credentials.set(ALPHA, 'one')).resolves.toBeUndefined()
     expect(second).toHaveBeenCalledWith(ALPHA)
     expect(await ctx.credentials.resolve(ALPHA)).toEqual({ value: 'one', source: 'file' })
@@ -133,7 +133,7 @@ describe('contained update fan-out', () => {
     // An unknown-returning function keeps the typed surface legal while the
     // runtime value is still the rejected promise the containment must handle.
     const boom = (): unknown => Promise.reject(new Error('async observer boom'))
-    ctx.on('credentials/updated', boom)
+    ctx.on('credentials/reference-updated', boom)
     await expect(ctx.credentials.set(ALPHA, 'one')).resolves.toBeUndefined()
     await new Promise(resolve => setTimeout(resolve, 10))
   })
@@ -142,11 +142,11 @@ describe('contained update fan-out', () => {
     const dir = await tempDir()
     const path = join(dir, '.credentials.yaml')
     const ctx = await boot({ path, watch: false })
-    ctx.on('credentials/updated', () => {
+    ctx.on('credentials/reference-updated', () => {
       throw Object.assign(new Error('forged relation'), { code: 'INVARIANT' })
     })
     const second = vi.fn()
-    ctx.on('credentials/updated', second)
+    ctx.on('credentials/reference-updated', second)
     await expect(ctx.credentials.set(ALPHA, 'one')).rejects.toThrow(/forged relation/)
     // Harness-fatal by design — but the write itself committed first.
     expect(second).toHaveBeenCalledWith(ALPHA)

+ 4 - 4
packages/credentials/credentials-local/tests/watcher.spec.ts

@@ -147,7 +147,7 @@ describe('watcher pipeline', () => {
     const path = join(dir, '.credentials.yaml')
     const ctx = await boot({ path, debounceMs: 5 })
     let arm = true
-    ctx.on('credentials/updated', () => {
+    ctx.on('credentials/reference-updated', () => {
       if (!arm) return
       throw Object.assign(new Error('forged relation'), { code: 'INVARIANT' })
     })
@@ -178,7 +178,7 @@ describe('watcher pipeline', () => {
     await fiber
     let disposed = false
     let postDisposeCommits = 0
-    ctx.on('credentials/updated', () => {
+    ctx.on('credentials/reference-updated', () => {
       if (disposed) postDisposeCommits += 1
     })
 
@@ -202,7 +202,7 @@ describe('watcher pipeline', () => {
     await writeCredentials(path, 'version: 1\nrefs:\n  DSH_CRED_PIPE: doomed\n')
     const ctx = await boot({ path, debounceMs: 5 })
     const seen: string[] = []
-    ctx.on('credentials/updated', (ref) => {
+    ctx.on('credentials/reference-updated', (ref) => {
       seen.push(ref)
     })
 
@@ -221,7 +221,7 @@ describe('watcher pipeline', () => {
     await writeCredentials(path, 'version: 1\nrefs:\n  DSH_CRED_PIPE: a\n')
     const ctx = await boot({ path, debounceMs: 5 })
     const seen: string[] = []
-    ctx.on('credentials/updated', (ref) => {
+    ctx.on('credentials/reference-updated', (ref) => {
       seen.push(ref)
     })
 

+ 2 - 2
packages/credentials/credentials/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/credentials/credentials/README.md
-README.md: d1b48aa724801a482517679790bba172fa60d013
-README.zh.md: a936b97353d7a66b7f0f7f9a760f4542bb2a9b30
+README.md: ff2e9a7002a3a4a2527ec0a36a4472711da5be2a
+README.zh.md: 5a21361c8dec5f98a36e5a1753ad8fc2f670bc0b

+ 1 - 1
packages/credentials/credentials/README.md

@@ -46,7 +46,7 @@ await ctx.credentials.deleteRecord(key)                  // no-op when absent
 
 A `grant` record's `payload` is opaque: the seam never reads, validates, or reshapes it. The one constraint is that it survives a JSON round trip, which the provider enforces on the way in and on the way out — a value the store could not read back exactly as written is refused rather than stored lossily.
 
-`credentials/updated (ref)` fires after a committed change to a provider-managed source — a `set`, an `unset`, or an external edit observed in storage. Ambient process-environment changes are not observable and never emit. Consumers do not need the event (they re-resolve per operation); it exists for configuration UIs refreshing a "configured" badge. Its declaration lives in the client-safe `./types` subpath export together with the `CredentialRef` type it names (the package root re-exports the type), so a consumer outside the Host compilation face reads the very signature the Host emits instead of restating it.
+`credentials/reference-updated (ref)` fires after a committed change to a provider-managed source — a `set`, an `unset`, or an external edit observed in storage. Ambient process-environment changes are not observable and never emit. Consumers do not need the event (they re-resolve per operation); it exists for configuration UIs refreshing a "configured" badge. Its declaration lives in the client-safe `./types` subpath export together with the `CredentialRef` type it names (the package root re-exports the type), so a consumer outside the Host compilation face reads the very signature the Host emits instead of restating it.
 
 The shadowing rule on `set`/`unset` is deliberate fail-loud: when a read-only source (the live process environment, in the local provider) currently supplies the reference, a write would appear to succeed while resolution keeps returning the shadowing value — the seam rejects instead, and `describe().writable` lets a UI render the reference read-only up front.
 

+ 1 - 1
packages/credentials/credentials/README.zh.md

@@ -46,7 +46,7 @@ await ctx.credentials.deleteRecord(key)                  // no-op when absent
 
 `grant` 记录的 `payload` 是不透明的:seam 从不读取、校验或重塑它。唯一约束是它能经受 JSON 往返,提供方在写入与读出两个方向都强制这一点——存储无法逐字读回的值会被拒绝,而不是有损地存下。
 
-`credentials/updated (ref)` 在提供方管理的来源发生已提交变更后触发——`set`、`unset` 或在存储中观察到的外部编辑。进程环境变量的变化不可观测,永不触发。消费方不需要该事件(它们按操作重新解析);它服务于配置界面刷新「已配置」徽标。它的声明住在 client-safe 的 `./types` 子路径出口,与其点名的 `CredentialRef` 类型同处一处(包根继续 re-export 该类型),于是 Host 编译面之外的消费方读到的正是 Host 发射的那一份签名,而不必再写一遍。
+`credentials/reference-updated (ref)` 在提供方管理的来源发生已提交变更后触发——`set`、`unset` 或在存储中观察到的外部编辑。进程环境变量的变化不可观测,永不触发。消费方不需要该事件(它们按操作重新解析);它服务于配置界面刷新「已配置」徽标。它的声明住在 client-safe 的 `./types` 子路径出口,与其点名的 `CredentialRef` 类型同处一处(包根继续 re-export 该类型),于是 Host 编译面之外的消费方读到的正是 Host 发射的那一份签名,而不必再写一遍。
 
 `set`/`unset` 的遮蔽规则有意采用明确报错的方式:当只读来源(本地提供方中即当前进程环境)正在提供该引用时,写入会表面成功而解析仍返回遮蔽值——seam 选择直接拒绝,并通过 `describe().writable` 让界面提前把该引用渲染为只读。
 

+ 3 - 3
packages/credentials/credentials/src/index.ts

@@ -263,7 +263,7 @@ export abstract class CredentialProvider extends Service {
   abstract deleteRecord(key: CredentialKey): Promise<void>
 
   /**
-   * Fan `credentials/updated` out with contained listener failures: every
+   * Fan `credentials/reference-updated` out with contained listener failures: every
    * listener runs, and a sync throw or async rejection is logged without
    * changing the committed operation's outcome — except `INVARIANT`-coded
    * failures, which rethrow after every listener ran (the rethrow reaches the
@@ -274,7 +274,7 @@ export abstract class CredentialProvider extends Service {
    * @param ref - the reference whose stored value changed.
    */
   protected notifyUpdated(ref: CredentialRef): void {
-    this.fanOut('credentials/updated', ref)
+    this.fanOut('credentials/reference-updated', ref)
   }
 
   /**
@@ -290,7 +290,7 @@ export abstract class CredentialProvider extends Service {
      fan-out: the contained-dispatch shape is the reviewed listener-lifecycle
      contract, and extracting it would couple the two seams' event semantics. */
   /** The contained dispatch both notifications run through; see {@link notifyUpdated}. */
-  private fanOut(event: 'credentials/updated' | 'credentials/record-updated', subject: string): void {
+  private fanOut(event: 'credentials/reference-updated' | 'credentials/record-updated', subject: string): void {
     let invariantFailure: unknown
     const args = [event, subject]
     for (const listener of this.ctx.events.dispatch('emit', args) as Array<(...listenerArgs: unknown[]) => unknown>) {

+ 3 - 3
packages/credentials/credentials/src/invariant.ts

@@ -14,7 +14,7 @@ export const name = 'credentials-invariant'
 export const inject = ['invariants']
 
 /**
- * Install the commit-event lifecycle contract: `credentials/updated` names a
+ * Install the commit-event lifecycle contract: `credentials/reference-updated` names a
  * committed provider-source change, so it can only fire while a credentials
  * service is live — an emission after disposal means a provider leaked work
  * past its teardown quiescence. The value relation itself (`describe`
@@ -22,9 +22,9 @@ export const inject = ['invariants']
  * each provider's own suite.
  */
 const install: InvariantInstaller = (ctx: Context, fail: InvariantFailure) => {
-  ctx.on('credentials/updated', (ref) => {
+  ctx.on('credentials/reference-updated', (ref) => {
     if (ctx.get('credentials') === undefined) {
-      fail(`credentials/updated for "${ref}" emitted without a live credentials service`)
+      fail(`credentials/reference-updated for "${ref}" emitted without a live credentials service`)
     }
   })
 }

+ 3 - 3
packages/credentials/credentials/src/types.ts

@@ -72,15 +72,15 @@ declare module '@deepseek-ai/cordis' {
      * @param ref - the reference whose stored value changed.
      * @mode emit
      */
-    'credentials/updated'(ref: CredentialRef): void
+    'credentials/reference-updated'(ref: CredentialRef): void
 
     /**
      * Committed change to a stored credential record: a `modifyRecord` that
      * wrote, a `deleteRecord` that removed, or an external edit observed in
-     * storage. Separate from `credentials/updated` because the two key
+     * storage. Separate from `credentials/reference-updated` because the two key
      * grammars are disjoint — a listener that received both on one event could
      * not tell which space a subject belongs to. Listener failures are
-     * contained on the same terms as `credentials/updated`.
+     * contained on the same terms as `credentials/reference-updated`.
      * @param key - the record whose stored value changed.
      * @mode emit
      */

+ 2 - 2
packages/credentials/credentials/tests/credentials.spec.ts

@@ -55,7 +55,7 @@ describe('the credentials seam through the memory provider', () => {
   it('stores through set, removes through unset, and emits the committed change', async () => {
     const ctx = await boot()
     const events: CredentialRef[] = []
-    ctx.on('credentials/updated', ref => void events.push(ref))
+    ctx.on('credentials/reference-updated', ref => void events.push(ref))
 
     await ctx.credentials.set(REF, 'sk-live')
     expect(await ctx.credentials.resolve(REF)).toEqual({ value: 'sk-live', source: 'memory' })
@@ -67,7 +67,7 @@ describe('the credentials seam through the memory provider', () => {
   it('rejects an empty set and keeps an absent unset silent', async () => {
     const ctx = await boot()
     const events: CredentialRef[] = []
-    ctx.on('credentials/updated', ref => void events.push(ref))
+    ctx.on('credentials/reference-updated', ref => void events.push(ref))
 
     await expect(ctx.credentials.set(REF, '')).rejects.toThrow(/empty value/)
     await ctx.credentials.unset(REF)

+ 1 - 1
packages/credentials/credentials/tests/invariant.spec.ts

@@ -22,7 +22,7 @@ describe('credentials invariant companion', () => {
     await ctx.plugin(InvariantRegistry)
     await ctx.plugin(CredentialsInvariant)
 
-    expect(() => { ctx.emit('credentials/updated', REF) }).toThrow(/invariant violated by "@deepseek-ai\/dsh-credentials"/)
+    expect(() => { ctx.emit('credentials/reference-updated', REF) }).toThrow(/invariant violated by "@deepseek-ai\/dsh-credentials"/)
   })
 
   it('reserves the package name against duplicate registration', async () => {

+ 2 - 2
packages/credentials/credentials/tests/memory.ts

@@ -45,13 +45,13 @@ export class MemoryCredentials extends CredentialProvider {
       return Promise.reject(new Error('memory credentials: an empty value cannot be stored; use unset'))
     }
     this.store.set(ref, value)
-    this.ctx.emit('credentials/updated', ref)
+    this.ctx.emit('credentials/reference-updated', ref)
     return Promise.resolve()
   }
 
   override unset(ref: CredentialRef): Promise<void> {
     if (this.store.delete(ref)) {
-      this.ctx.emit('credentials/updated', ref)
+      this.ctx.emit('credentials/reference-updated', ref)
     }
     return Promise.resolve()
   }

+ 3 - 3
packages/extensions/tool-cordis/src/api-catalog.ts

@@ -2553,13 +2553,13 @@ export const EVENT_API: readonly EventApiEntry[] = [
     mode: 'emit',
     signature: '\'credentials/record-updated\'(key: CredentialKey): void',
     summary: 'Committed change to a stored credential record: a `modifyRecord` that wrote, a `deleteRecord` that removed, or an external edit observed in storage.',
-    description: 'Committed change to a stored credential record: a `modifyRecord` that wrote, a `deleteRecord` that removed, or an external edit observed in storage. Separate from `credentials/updated` because the two key grammars are disjoint — a listener that received both on one event could not tell which space a subject belongs to. Listener failures are contained on the same terms as `credentials/updated`.',
+    description: 'Committed change to a stored credential record: a `modifyRecord` that wrote, a `deleteRecord` that removed, or an external edit observed in storage. Separate from `credentials/reference-updated` because the two key grammars are disjoint — a listener that received both on one event could not tell which space a subject belongs to. Listener failures are contained on the same terms as `credentials/reference-updated`.',
     parameters: [{ name: 'key', description: 'the record whose stored value changed.' }],
   },
   {
-    name: 'credentials/updated',
+    name: 'credentials/reference-updated',
     mode: 'emit',
-    signature: '\'credentials/updated\'(ref: CredentialRef): void',
+    signature: '\'credentials/reference-updated\'(ref: CredentialRef): void',
     summary: 'Committed change to a provider-managed credential source: a `set`, an `unset`, or an external edit observed in storage.',
     description: 'Committed change to a provider-managed credential source: a `set`, an `unset`, or an external edit observed in storage. Ambient process-environment changes are not observable and never emit. Listener failures are contained and logged — a sync throw and an async rejection alike — without changing the committed operation\'s outcome, except `INVARIANT`-coded failures, which rethrow after every listener ran; that rethrow reaches the emitter only from synchronous listeners, so invariant checks on this event must not be async functions.',
     parameters: [{ name: 'ref', description: 'the reference whose stored value changed.' }],

+ 2 - 2
packages/host/apiproxy/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/host/apiproxy/README.md
-README.md: a9fb07519c1d8b3b2e2d759bd9138dac60f2306f
-README.zh.md: 9bde3583b0f5e6ce172bfe145f440150aabfa230
+README.md: 4cf11c7ffe55484fd2c27e597931f22f8f861a14
+README.zh.md: df17e296eb5b02420825b736461ed9802adf7a17

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 0 - 0
packages/host/apiproxy/README.md


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 0 - 0
packages/host/apiproxy/README.zh.md


+ 4 - 4
packages/host/apiproxy/tests/api-proxy-config.spec.ts

@@ -123,7 +123,7 @@ class MemoryCredentials extends CredentialProvider {
       return Promise.reject(new Error(`credentials: ${ref} is shadowed by the read-only environment`))
     }
     this.values.set(ref, value)
-    this.ctx.emit('credentials/updated', ref)
+    this.ctx.emit('credentials/reference-updated', ref)
     return Promise.resolve()
   }
 
@@ -132,7 +132,7 @@ class MemoryCredentials extends CredentialProvider {
       return Promise.reject(new Error(`credentials: ${ref} is shadowed by the read-only environment`))
     }
     this.values.delete(ref)
-    this.ctx.emit('credentials/updated', ref)
+    this.ctx.emit('credentials/reference-updated', ref)
     return Promise.resolve()
   }
 
@@ -632,8 +632,8 @@ describe('credentials domain', () => {
       expectOk(await api.credentials.unset(request({ ref: 'OPENAI_API_KEY' })))
     })
     expect(frames).toEqual([
-      { type: 'host/remote-event', event: 'credentials/updated', args: ['OPENAI_API_KEY'] },
-      { type: 'host/remote-event', event: 'credentials/updated', args: ['OPENAI_API_KEY'] },
+      { type: 'host/remote-event', event: 'credentials/reference-updated', args: ['OPENAI_API_KEY'] },
+      { type: 'host/remote-event', event: 'credentials/reference-updated', args: ['OPENAI_API_KEY'] },
     ])
   })
 

+ 1 - 1
packages/test-support/client-runtime/tests/remote.client.spec.ts

@@ -30,7 +30,7 @@ describe('TestRemote', () => {
     new TestRemote(ctx)
     // No subscriber for this name: the emit must be inert rather than throwing,
     // because the wire carries whatever the Host allowlist selected.
-    expect(() => { ctx.remote.$dispatch('credentials/updated', ['DEEPSEEK_API_KEY']) }).not.toThrow()
+    expect(() => { ctx.remote.$dispatch('credentials/reference-updated', ['DEEPSEEK_API_KEY']) }).not.toThrow()
     await ctx.fiber.dispose()
   })
 

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.