cleanup(config): remove literal credential compatibility residue

Adapter schemas now carry only credential references, but the Models join, onboarding readiness, shipped overlays, SDK scaffolding, fixtures, and active decision prose still treated a redacted literal apiKey as a supported compatibility state. That residue made an unsupported field look contractual and pinned Schemastery silent-dropping as behavior.

Delete those branches and examples, and let compositions and scaffolds use adapter-owned reference and environment resolution. Do not add a tombstone validator or change generic unknown-key behavior: literal adapter credentials have no migration contract to preserve.
This commit is contained in:
Tianyi Cui
2026-08-07 22:03:49 +08:00
parent ac154b2dfa
commit 356453d6cb
37 changed files with 78 additions and 189 deletions
@@ -80,8 +80,8 @@ function renderProviderEditor({ target, ...props }: ProviderEditorRenderProps):
* Remove one user-added provider and its page-managed credential. Credential
* removal comes first so a second-step failure leaves the provider row visible
* and the whole operation safely retryable; both unsets are idempotent.
* The settings removal names the profile rather than rebuilding its redacted
* namespace, which would drop literal secrets stored elsewhere.
* The settings removal names the profile rather than rebuilding its whole
* namespace from a partial view.
* @param api - settings and credential wire faces.
* @param controller - the page store to refresh.
* @param target - the provider's settings address and optional managed credential.
@@ -112,16 +112,14 @@ export async function removeProviderProfile(
}
/**
* Whether a whole-section provider still needs its first key: nothing marks
* the credential configured and no literal `apiKey` is stored, so the page
* opens the setup card instead of showing a row.
* Whether a whole-section provider still needs its first key: an unconfigured
* credential opens the setup card instead of showing a row.
* @param row - the joined provider row.
* @returns whether to render the setup card.
*/
export function needsSetup(row: ProviderRow): boolean {
if (row.entry.settingsPath.length > 0) return false
if (row.credential?.configured === true) return false
return !row.literalApiKeyConfigured
return row.credential?.configured !== true
}
function targetOf(row: ProviderRow): EditorTarget {
@@ -264,7 +262,7 @@ function Loaded({ injected }: { injected: ModelsSectionInjected }): ReactNode {
)
}
const open = !adding && editing?.provider === row.entry.provider
const credentialConfigured = row.literalApiKeyConfigured || row.credential?.configured === true
const credentialConfigured = row.credential?.configured === true
const credentialMissing = !credentialConfigured
&& row.apiKeyEnv !== undefined
&& row.credential?.configured === false
@@ -10,9 +10,8 @@
* both families, `reasoningEffort` for deepseek / `reasoning` for pi-ai, and
* DeepSeek's id/name/context-window model catalog). Everything else stays
* owned by `settings.yaml`. Profile edits land as minimal `settings.mutate`
* path ops against the stored section — the card reads the redacted
* descriptor, so it names only the fields it can see and a stored literal
* secret is never collaterally removed.
* path ops against the stored section — the card names only the fields it can
* see instead of rebuilding the whole subtree from a partial descriptor.
*/
import { useEffect, useMemo, useState } from 'react'
@@ -80,10 +79,9 @@ function draftAt(namespace: SettingsNamespaceView, path: readonly string[]): Rec
/**
* The minimal path ops carrying `after` over `before`, both as the card sees
* them (that is, redacted). Only keys the card observed are named: a stored
* `role('secret')` field appears in neither side, so it produces no op and
* survives the write — the whole reason edits are path-addressed rather than
* a rebuilt section.
* them. Only keys the card observed are named; fields absent from both sides
* produce no op, which is why edits are path-addressed rather than a rebuilt
* section.
* @param base - path of the edited subtree inside the user section.
* @param before - the subtree as loaded, or undefined when it is new.
* @param after - the subtree as edited.
@@ -205,9 +203,8 @@ export function ProviderEditor(props: ProviderEditorProps): ReactNode {
/**
* The write for this card, or a failure message. Every edit travels as
* path ops against the STORED section: the draft comes from the redacted
* descriptor, so a wholesale replace rebuilt from it would delete the
* literal secrets the wire never returned. Ops name only the fields this
* card can see, so a stored secret is untouched by construction.
* descriptor, so a wholesale replace rebuilt from it could delete fields
* outside the card. Ops name only the fields this card can see.
*/
const applyOnce = async (): Promise<string | undefined> => {
const ns = namespace.ns
@@ -31,8 +31,6 @@ export interface ProviderRow {
apiKeyEnv: string | undefined
/** Credential state for {@link apiKeyEnv}, once described. */
credential: CredentialView | undefined
/** Whether the redacted secret sidecar reports an effective literal `apiKey`. */
literalApiKeyConfigured: boolean
}
/** Page snapshot. */
@@ -97,19 +95,6 @@ function apiKeyEnvOf(namespace: SettingsNamespaceView | undefined, path: readonl
return typeof ref === 'string' && ref.length > 0 ? ref : undefined
}
/** Whether one namespace's redacted sidecar reports a set literal API key. */
function literalApiKeyConfigured(
namespace: SettingsNamespaceView | undefined,
path: readonly string[],
): boolean {
if (namespace === undefined) return false
const secretPath = [...path, 'apiKey']
return namespace.secrets.some(secret =>
secret.set
&& secret.path.length === secretPath.length
&& secret.path.every((key, index) => key === secretPath[index]))
}
/** The models settings page controller (one per settings surface). */
export class ModelsSettingsStore {
/** The snapshot the section renders from (uSES-safe store). */
@@ -170,7 +155,6 @@ export class ModelsSettingsStore {
removable,
apiKeyEnv: apiKeyEnvOf(namespace, entry.settingsPath),
credential: undefined,
literalApiKeyConfigured: literalApiKeyConfigured(namespace, entry.settingsPath),
}
})
const refs = [...new Set(rows.flatMap(row => row.apiKeyEnv === undefined ? [] : [row.apiKeyEnv]))]
@@ -257,7 +241,6 @@ export function deepSeekReadiness(state: ModelsSettingsState): DeepSeekReadiness
reason: 'settings-unavailable',
}
}
if (row.literalApiKeyConfigured) return { kind: 'configured' }
if (row.apiKeyEnv === undefined) {
return {
kind: 'unavailable',