/** * credential-scope-registry.ts, every credential this platform stores, and * whether the daemon needs it. * * The rule, in one sentence, and it is the owner's: * * Anything configured on one surface is available to the daemon, even after * that surface has closed. So any credential a daemon-side capability needs * in order to function is written at DAEMON scope, no matter which surface * captured it. * * What went wrong without this. `daemon-secret-keys.ts` already derives daemon * ownership from daemon-owned CONFIG paths, and that works, for credentials a * config path names. It cannot see the others. `SLACK_BOT_TOKEN`, * `CLOUDFLARE_API_TOKEN`, `GOODVIBES_CALENDAR_GOOGLE_TOKENS`, the relay identity * keypair, the per-subscription calendar feed keys: bare names an operator or a * subsystem invented, that nothing derives, so nothing filed them in the daemon * tier. Each went to whichever silo the capturing surface happened to use, and * the daemon, the process that actually sends the Slack message, opens the * tunnel, reads the calendar, could not read any of them. The symptom is always * the same and always looks like something else: a capability that reports * itself unconfigured while working credentials sit on the same disk. * * It appeared once with Google mail (`/google adopt` succeeded in the agent, and * the daemon answering Telegram said no email integration was available), once * with payment card fields (the settings modal wrote at user scope while the * command wrote at daemon scope), and once with the Telegram bot username. Three * incidents, one defect. * * ── How this file makes the wrong thing hard ──────────────────────────────── * * 1. Every credential key is DECLARED here with a typed scope and a reason. * `daemon-needed` or `surface-local`, there is no third answer and no * default, so "which is it" is a question someone answered on purpose. * * 2. `isDaemonNeededSecretKey` consults this registry, and `SecretsManager.set` * consults that. A `daemon-needed` key requested at any other scope is * RELOCATED to the daemon tier and the relocation is logged. The write is * never dropped: refusing it would put a wall in front of the credentials * people most need to set, and the caller's scope argument is nearly always * a default it never thought about rather than an intent. * * 3. `scripts/check-credential-scope.ts` walks every `secrets.set(...)` call * site in the SDK and fails the build on a credential key that is not * declared here. Adding a credential without classifying it does not compile * past the gate, which is the part that stops this recurring. * * ── What counts as surface-local ──────────────────────────────────────────── * * A credential is surface-local ONLY when the daemon can never be the thing * that uses it. That is a narrow set: state that is meaningless off the machine * or the process that made it. It is NOT "the surface captured it", and it is * NOT "only that surface has UI for it", the daemon does the work whichever * surface set it up. */ /** Whether the daemon is ever the process that uses this credential. */ export type CredentialScopeClass = /** The daemon executes with it. Daemon tier, whichever surface captured it. */ 'daemon-needed' /** Only the capturing process can ever use it. Stays in that surface's store. */ | 'surface-local'; /** One declared credential. */ export interface CredentialScopeDeclaration { /** The secret-store key, or a prefix when the suffix is user-chosen (see `match`). */ readonly key: string; /** * `exact`, the key is this literal string. * `prefix`, the key starts with this string; the rest is a name a person or * a provider chose (a calendar subscription's name, a provider id). */ readonly match: 'exact' | 'prefix'; readonly scope: CredentialScopeClass; /** Which daemon-side capability needs it, or why the daemon can never want it. */ readonly why: string; } /** * The declarations. * * Grouped by the capability that reads them, because that grouping is the * argument: if the daemon performs the capability, the credential is the * daemon's, and the group makes it obvious when one member has been left behind. */ export declare const CREDENTIAL_SCOPE_DECLARATIONS: readonly CredentialScopeDeclaration[]; /** The declaration covering `key`, or null when nothing declares it. */ export declare function findCredentialScopeDeclaration(key: string): CredentialScopeDeclaration | null; /** * True when the daemon is the reader-of-record for this credential. * * Two sources, deliberately: a credential a daemon-owned CONFIG path names * (derived, never hand-maintained, see daemon-secret-keys.ts), and a * credential declared above (bare names nothing derives). Either one is * sufficient; a key covered by neither keeps the scope its caller asks for. */ export declare function isDaemonNeededSecretKey(key: string): boolean; /** * The plain-language reason a credential is filed where it is. Used by the * relocation log line and by surfaces that tell an operator, before asking for * a credential, where it is about to go. */ export declare function describeCredentialScope(key: string): string; /** Every declared daemon-needed key that matches exactly. Used by migration. */ export declare function listExactDaemonNeededKeys(): readonly string[]; /** Every declared daemon-needed key PREFIX. Used by migration to sweep families. */ export declare function listDaemonNeededKeyPrefixes(): readonly string[]; //# sourceMappingURL=credential-scope-registry.d.ts.map