Code already treats $DSH_HOME/.env as ordinary launch environment and stores managed credentials in .credentials.yaml, but public docs still described the old store, old precedence, removed literal adapter keys, and the deleted TUI. That directed users to the wrong file and overstated the supported configuration surface. Update the existing English and Chinese owners in place, document inherited > managed > project > user credential resolution, and record the loadLayeredEnv export. Regenerate only pairing records and the source-line catalog; add no new section or site route.
49 lines
3.5 KiB
Markdown
49 lines
3.5 KiB
Markdown
# dsh-credentials
|
|
|
|
English | [中文](README.zh.md)
|
|
|
|
Abstract credential seam (`ctx.credentials`). One doctrine, three consequences:
|
|
|
|
**Configuration carries references to secrets, never the secrets.** A settings section or `cordis.yml` entry says `apiKeyEnv: DEEPSEEK_API_KEY`; the value behind that reference lives with a credential provider. So the settings document stays safe to sync and to render in a configuration UI, `describe()` can answer "is this configured, where from, can I write it" without ever holding a value, and rotating a secret touches no configuration file.
|
|
|
|
**Consumers resolve per operation.** `resolve(ref)` is called at the start of each operation (the LLM adapters resolve once per model request) and never cached across operations — that read is what makes a changed credential reach the very next request without restarting any plugin.
|
|
|
|
**An empty stored value is absent.** Everywhere: `resolve` skips it, `describe` reports it unconfigured. A blank can never masquerade as a configured secret.
|
|
|
|
## Surface
|
|
|
|
```ts
|
|
import type { Context } from 'cordis'
|
|
import { credentialRef } from '@deepseek-ai/dsh-credentials'
|
|
|
|
declare const ctx: Context
|
|
|
|
const ref = credentialRef('DEEPSEEK_API_KEY') // POSIX shell identifier, branded
|
|
const hit = await ctx.credentials.resolve(ref) // { value, source } | undefined
|
|
const info = await ctx.credentials.describe(ref) // { configured, source?, writable } — never the value
|
|
await ctx.credentials.set(ref, 'sk-…') // rejects while a read-only source shadows the ref
|
|
await ctx.credentials.unset(ref) // no-op when absent; same shadowing rule
|
|
```
|
|
|
|
`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.
|
|
|
|
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.
|
|
|
|
## Providers
|
|
|
|
[`dsh-credentials-local`](../credentials-local/README.md) layers the inherited process environment over its managed `$DSH_HOME/.credentials.yaml` document, with the launcher's project and user `.env` layers as fallbacks. The seam shape leaves room for keyring-, helper-command-, and KMS-backed providers; a remote settings provider never needs to carry secrets.
|
|
|
|
## Model Experience
|
|
|
|
Indirectly, through the consuming LLM adapters: a resolved value authorizes their provider requests, and the adapter owns every model-visible surface.
|
|
|
|
#### KV Cache effect
|
|
|
|
No direct invalidation; credentials never enter a request prefix.
|
|
|
|
## Known Limitations and Deferred Work
|
|
|
|
- **No enumeration** — the seam answers questions about references it is given; configuration surfaces learn the references from settings schemas, so a `list()` has no current consumer.
|
|
- **References are environment-variable-shaped** — one flat POSIX-identifier namespace until a provider needs richer addressing.
|
|
- **Process-environment changes are invisible** — no event can fire for them; a UI only re-reads `describe()` on its own navigation.
|