/** * Services-credentials check. * * For each registered container service, verifies its API credentials * are present and decryptable with the current master key. Catches * failure modes that today only surface at deploy time: * * - Service registered but credentials never set (column null/empty). * - Master key rotated; old envelope no longer decrypts. * - Stored envelope is corrupt or doesn't match the provider's * credential schema (e.g. Proxmox service has DigitalOcean-shaped * credentials). * * Each finding is BLOCKED — `system update` would otherwise fan * these out as opaque terraform / SSH errors. Surfacing them up * front lets the user re-run `celilo service set-credentials` * before any deploy work. */ import type { DriftFinding } from './types'; export interface ServiceCredentialsResult { /** Stable service identifier (the user-facing kebab-case ID). */ serviceId: string; /** Display name for the finding message. */ name: string; providerName: string; /** null on success; error message on failure. */ error: string | null; } export interface ServicesCredentialsAuditDeps { results: ServiceCredentialsResult[]; } export async function auditServicesCredentials( deps: ServicesCredentialsAuditDeps, ): Promise { const findings: DriftFinding[] = []; for (const r of deps.results) { if (r.error === null) continue; findings.push({ category: 'services_credentials', severity: 'blocked', code: 'service_credentials_invalid', message: `${r.name} (${r.providerName}): credentials missing or invalid`, details: r.error, remediation: [ 'Re-add the credentials for this service. For example:', ` celilo service set-credentials ${r.serviceId}`, 'or remove the service:', ` celilo service remove ${r.name}`, ].join('\n'), // Multi-step (interactive prompts for credential values), so // not a one-keypress remediation in the TUI. actionable: false, subject: r.serviceId, }); } return findings; }