refactor(credentials,llm): remove speculative mutation and route lifecycle

This commit is contained in:
Tianyi Cui
2026-07-31 01:08:54 +08:00
parent 16802cd612
commit f9f8148e79
100 changed files with 559 additions and 2752 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 docs/core-data-structures/core.md
core.md: 09b437a8483134230d4b941c20940c5655bc53f0
core.zh.md: 2025707db397203dbaec52c59172f83367f2033e
core.md: fa313fee4f29c79ae0713b156ed248cca7285e21
core.zh.md: be604b24474f2308ddb31d50ae88b73e6aaa4084

View File

@@ -25,7 +25,7 @@ Everything else is documented on a **sub-page**, not here. The rule that draws t
| [session.md](session.md) | the full `SessionEventMap` variant catalog, `TurnTrigger`/`TurnEndReason`, `deriveMessages()`, execution enclosure, and standalone events |
| [persistence.md](persistence.md) | the durability seam: `SessionPersistence`, JSONL + SQLite backends, `session/flush`, crash recovery, `SessionHeader` |
| [settings.md](settings.md) | the user-settings seam: `SettingsNamespace` registration, layered resolution (defaults → composition `base` → user document), owner scopes, hot commits |
| [credentials.md](credentials.md) | the credential seam: `CredentialRef` references (never values) in configuration, per-operation resolution, UI-safe `CredentialInfo`, provider source layers |
| [credentials.md](credentials.md) | the read-only credential seam: `CredentialRef` references and per-operation value resolution |
| [session-query.md](session-query.md) | logical records, bounded exact-event reads, relationship traces, semantic filters/documents, and full-text result pages |
| [session-title.md](session-title.md) | durable title snapshots, source provenance, and the asynchronous provider contract |
| [system-prompt.md](system-prompt.md) | per-assembly context, tool-provider results, prompt sections, and cooperative assembly |
@@ -183,33 +183,7 @@ Source: [`packages/llm/llm/src/types.ts`](../../packages/llm/llm/src/types.ts)
Provider and model discovery uses small provider-neutral descriptors. A model catalog is advisory: routing still keys on a registered provider, and an adapter may accept unlisted model ids.
Registering an adapter returns a handle: the disposer, plus the atomic route replacement a plugin whose route set is user-configurable needs.
```ts type-equiv
/**
* What {@link LlmService.registerAdapter} returns: the disposer, plus an
* atomic route replacement for the same adapter instance.
*/
interface AdapterRegistrationHandle {
/** Release every route this registration currently holds. */
(): void
/**
* Replace this registration's routes with `providers`, keeping the same
* adapter instance. The candidate set is validated in full first — a
* conflict with another adapter, an invalid name, or bad provider metadata
* throws and leaves the current routes untouched — and the swap itself is
* one synchronous section, so no request can observe a gap. An empty array
* is legal here (a settings section that emptied holds zero routes while
* staying registered), unlike an empty initial registration.
*
* Throws `LlmError` with code `REGISTRATION_DISPOSED` once the registration
* has been released: its routes are gone and its disposer has already run,
* so anything registered afterwards would have no owner left to release it.
* @param providers - the complete next route set for this registration.
*/
replace(providers: string[]): void
}
```
Registering an adapter binds one non-empty provider-route set to the calling fiber and returns its disposer. Routes are composition-owned rather than mutable user settings.
```ts type-equiv
/** Display metadata for one registered provider route. */

View File

@@ -25,7 +25,7 @@ harness 是一个微内核:一个极小的核心加上众多插件。大多数
| [session.md](session.md) | 完整的 `SessionEventMap` 变体目录、`TurnTrigger`/`TurnEndReason``deriveMessages()`、执行封闭与独立事件 |
| [persistence.md](persistence.md) | 持久性 seam`SessionPersistence`、JSONL + SQLite 后端、`session/flush`、崩溃恢复、`SessionHeader` |
| [settings.md](settings.md) | 用户设置 seam`SettingsNamespace` 注册、分层解析(默认值 → 组合 `base` → 用户文档、owner scope、热提交 |
| [credentials.md](credentials.md) | 凭据 seam配置中的 `CredentialRef` 引用(绝不含值)、按操作解析、对 UI 安全的 `CredentialInfo`、provider 来源层 |
| [credentials.md](credentials.md) | 只读凭据 seam`CredentialRef` 引用与按操作解析值 |
| [session-query.md](session-query.md) | 逻辑记录、有界精确事件读取、关系追踪、语义筛选器/文档与全文检索结果页 |
| [session-title.md](session-title.md) | 持久标题快照、来源 provenance 与异步提供方契约 |
| [system-prompt.md](system-prompt.md) | 逐次组装的上下文、工具提供方结果、提示词段落与协作式组装 |
@@ -189,33 +189,7 @@ interface MessageSourceMap {
提供方与模型发现使用小型、提供方无关的描述符。模型目录仅供参考:路由仍以已注册提供方为键,适配器也可以接受未列出的模型 id。
注册适配器会返回一个句柄:既是释放器,也带有原子的路由替换——路由集合由用户配置决定的插件正需要它
```ts type-equiv
/**
* What {@link LlmService.registerAdapter} returns: the disposer, plus an
* atomic route replacement for the same adapter instance.
*/
interface AdapterRegistrationHandle {
/** Release every route this registration currently holds. */
(): void
/**
* Replace this registration's routes with `providers`, keeping the same
* adapter instance. The candidate set is validated in full first — a
* conflict with another adapter, an invalid name, or bad provider metadata
* throws and leaves the current routes untouched — and the swap itself is
* one synchronous section, so no request can observe a gap. An empty array
* is legal here (a settings section that emptied holds zero routes while
* staying registered), unlike an empty initial registration.
*
* Throws `LlmError` with code `REGISTRATION_DISPOSED` once the registration
* has been released: its routes are gone and its disposer has already run,
* so anything registered afterwards would have no owner left to release it.
* @param providers - the complete next route set for this registration.
*/
replace(providers: string[]): void
}
```
注册适配器会把一组非空提供方路由绑定到调用方 fiber并返回相应的释放器。路由归组合所有不属于可变的用户设置
```ts type-equiv
/** Display metadata for one registered provider route. */

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 docs/core-data-structures/credentials.md
credentials.md: 3f6fcd127d01e2c49e17c70c002bebe9f363e951
credentials.zh.md: b5d2d9e164a85ce090790635c438b768cae4c9ca
credentials.md: 90e39a013f6b557ca5d4f07facebda390b908305
credentials.zh.md: 92986f484bcc271538d02169038bbe71ebd5e4a6

View File

@@ -2,7 +2,7 @@
English | [中文](credentials.zh.md)
The credential seam of [dsh-credentials](../../packages/credentials/credentials) keeps secrets out of configuration: settings sections and `cordis.yml` entries carry *references* (environment-variable names), providers such as [dsh-credentials-local](../../packages/credentials/credentials-local) own the values, and consumers resolve a reference once per operation — the LLM adapters resolve once per model request, so a rotated credential reaches the very next request without any restart. One seam-wide rule binds every provider: an empty stored value is absent everywhere.
The [dsh-credentials](../../packages/credentials/credentials) seam lets configuration name secrets by reference rather than carry their values. Providers such as [dsh-credentials-local](../../packages/credentials/credentials-local) resolve the current non-empty value, and consumers resolve once per operation so an external rotation reaches the next operation without a restart.
Source: [`packages/credentials/credentials/src/index.ts`](../../packages/credentials/credentials/src/index.ts)
@@ -17,34 +17,4 @@ type CredentialRef = Branded<'CredentialRef'>
## Resolution
`resolve(ref)` returns the value with the provider-defined source layer that supplied it, or `undefined` while unconfigured. Consumers re-resolve at each operation and never cache across operations — that per-operation read is the hot-update mechanism.
```ts type-equiv
/** One resolved credential value and the source layer that supplied it. */
interface ResolvedCredential {
/** The non-empty secret value. */
value: string
/** Provider-defined source layer id (the local provider uses `env` and `file`). */
source: string
}
```
## Description
`describe(ref)` answers configuration surfaces without ever exposing a value: whether the reference resolves, from which layer, and whether `set` would currently succeed. The local provider reports a reference supplied by the live process environment as `writable: false` — a write would appear to succeed while resolution kept returning the shadowing value, so the seam rejects it and the UI can render the reference read-only up front.
```ts type-equiv
/** Source and writability facts for one reference, safe for configuration UIs — never the value. */
interface CredentialInfo {
/** Whether {@link Credentials.resolve} would currently return a value. */
configured: boolean
/** Source layer currently supplying the value; absent while unconfigured. */
source?: string
/** Whether {@link Credentials.set} would currently succeed for this reference. */
writable: boolean
}
```
## 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.
`ctx.credentials.resolve(ref)` returns the provider's current non-empty secret string, or `undefined` while unconfigured. Consumers do not cache across operations. The seam deliberately exposes no mutation, source-description, enumeration, or change-event contract; the generated [service catalog](../cordis-catalog/services.md) owns the method signature.

View File

@@ -2,7 +2,7 @@
[English](credentials.md) | 中文
[dsh-credentials](../../packages/credentials/credentials) 的凭据 seam 把机密挡在配置之外settings 分节与 `cordis.yml` 条目携带的是*引用*(环境变量名),值归 [dsh-credentials-local](../../packages/credentials/credentials-local) 这类 provider 所有消费方每个操作解析一次引用——LLM 适配器每次模型请求解析一次,因此轮换后的凭据无需任何重启即可作用于紧随其后的下一次请求。一条 seam 级规则约束每个 provider空的存储值在任何地方都视为不存在
[dsh-credentials](../../packages/credentials/credentials) seam 允许配置以引用点名机密,而非携带机密值。[dsh-credentials-local](../../packages/credentials/credentials-local) 这类提供方解析当前非空值,消费方每个操作解析一次,因此从外部轮换的值无需重启即可作用于下一次操作
Source: [`packages/credentials/credentials/src/index.ts`](../../packages/credentials/credentials/src/index.ts)
@@ -17,34 +17,4 @@ type CredentialRef = Branded<'CredentialRef'>
## 解析
`resolve(ref)` 返回值,连同供出该值、由 provider 定义的来源层;未配置期间返回 `undefined`。消费方在每个操作中重新解析,绝不跨操作缓存——这次按操作进行的读取正是热更新机制。
```ts type-equiv
/** One resolved credential value and the source layer that supplied it. */
interface ResolvedCredential {
/** The non-empty secret value. */
value: string
/** Provider-defined source layer id (the local provider uses `env` and `file`). */
source: string
}
```
## 描述
`describe(ref)` 在绝不暴露值的前提下回应配置界面:引用当前是否可解析、来自哪一层、`set` 当前能否成功。本地 provider 把由活跃进程环境供值的引用报告为 `writable: false`——那样的写入会表面成功而解析持续返回遮蔽值,因此 seam 直接拒绝,界面也得以提前把该引用渲染为只读。
```ts type-equiv
/** Source and writability facts for one reference, safe for configuration UIs — never the value. */
interface CredentialInfo {
/** Whether {@link Credentials.resolve} would currently return a value. */
configured: boolean
/** Source layer currently supplying the value; absent while unconfigured. */
source?: string
/** Whether {@link Credentials.set} would currently succeed for this reference. */
writable: boolean
}
```
## 变更提交
`credentials/updated (ref)` 在 provider 管理的来源发生已提交变更后触发——`set`、`unset` 或在存储中观察到的外部编辑。进程环境自身的变化不可观测,永不发出事件。消费方不需要该事件(它们按操作重新解析);它服务于配置界面刷新「已配置」徽标。
`ctx.credentials.resolve(ref)` 返回提供方当前的非空机密字符串,未配置返回 `undefined`。消费方不跨操作缓存。该 seam 刻意不暴露变更、来源描述、枚举或变更事件契约;方法签名由生成的[服务目录](../cordis-catalog/services.md)负责。