/** * settings domain contract: the web face of the user-settings seam * (`ctx.settings`). Every payload that leaves this domain is redacted by the * seam (`describe({ redactSecrets: true })` semantics): `role('secret')` * fields never ride a response in any layer, and the `secrets` slot list is * how a form learns a write-only field exists and whether it is configured. */ import type { RpcRequest, RpcResponse } from './rpc.ts' /** One schema-declared secret slot inside a redacted namespace value. */ export interface SettingsSecretView { /** Path from the section root to the removed field. */ path: string[] /** Whether the slot currently holds a value (the value itself never rides). */ set: boolean } /** Wire view of one registered settings namespace. */ export interface SettingsNamespaceView { /** Namespace key (`llm-deepseek`, `llm-pi-ai`, …). */ ns: string /** Serialized schemastery schema envelope (`schema.toJSON()`); rehydrate with `new Schema(json)`. */ schema: unknown /** Redacted resolved value (schema defaults → composition base → user layer). */ value: unknown /** Redacted composition base layer, when the registrant declared one. */ base?: unknown /** Redacted raw user section, when one exists; a field's presence here marks it user-overridden. */ user?: unknown /** When the owner applies changes. */ applies: 'live' | 'restart' /** Every schema-declared secret slot with its configured state. */ secrets: SettingsSecretView[] /** * Monotonic revision of the raw user section this view was read at. Send it * back as `expectedRevision` on a write so a stale editor is refused rather * than silently overwriting a concurrent change. */ revision: number } /** * One path-addressed edit carried by `settings.mutate`. `set` writes the * value at the path (creating intermediate objects); `unset` removes it. The * empty path addresses the section root. */ export type SettingsPathOpView = | { op: 'set'; path: string[]; value: unknown } | { op: 'unset'; path: string[] } /** Settings-domain unary methods (the map keys settings.* of RpcMethodMap). */ export interface SettingsApi { /** * Describe every registered namespace: redacted layered values plus the * serialized schema a client renders its form from. `hasDocument` reports * whether a file-backed provider owns a local document without exposing its * Host path. This method is loopback-only; `writable: false` (read-only * provider) tells the client to disable every write control. */ describe(request: RpcRequest<{}>): Promise> /** * Materialize the configured local document when absent and ask the Host to * hand it to the platform text-document opener. macOS forces a text editor; * Linux and Windows use the desktop file association. The request carries * no path, so the browser cannot choose an arbitrary Host filesystem target. */ openDocument( request: RpcRequest<{}>, signal: AbortSignal, ): Promise> /** * Merge a patch into one namespace's user layer (validate → persist → * commit). Secret-role fields may be INCLUDED in the patch (write-only * direction); a form that leaves a secret untouched simply omits it and the * merge preserves the stored value. Responds with the namespace's new * redacted view; a schema or storage rejection is `settings-rejected`. */ update(request: RpcRequest<{ ns: string; patch: object; expectedRevision?: number }>): Promise> /** * Replace one namespace's user section wholesale — the removal/reset path a * merge cannot express (`section: {}` resets to composition defaults). Keys * absent from `section` are dropped, secrets included: a client must first * fold the descriptor's `user` layer (and re-supply any secret it wants to * keep) or accept the reset. */ replace(request: RpcRequest<{ ns: string; section: object; expectedRevision?: number }>): Promise> /** * Apply path-addressed edits to one namespace's user section, resolved * against the section as stored — NOT against whatever the caller last * read. This is the removal path for any client holding the redacted * descriptor: it names the field it means, so a secret the wire never * returned cannot be deleted as a side effect. `replace` remains the * deliberate wholesale reset. */ mutate( request: RpcRequest<{ ns: string; ops: SettingsPathOpView[]; expectedRevision?: number }>, ): Promise> }