package secretstore import ( "errors" "fmt" "strings" ) const DefaultManifestSource = "default:auto (manifest not found)" type DescribeRuntimeOptions struct { ServiceName string LookupEnv func(string) (string, bool) KWalletAppID string KWalletFolder string BitwardenCommand string BitwardenDebug bool DisableBitwardenCache bool CheckReady bool Shell string ManifestLoader ManifestLoader ExecutableResolver ExecutableResolver } type RuntimeDescription struct { ManifestSource string DeclaredPolicy BackendPolicy EffectivePolicy BackendPolicy DisplayName string Ready bool ReadyError error } type PreflightStatus string const ( PreflightStatusReady PreflightStatus = "ready" PreflightStatusFail PreflightStatus = "fail" ) type PreflightOptions = DescribeRuntimeOptions type PreflightReport struct { Status PreflightStatus Summary string Remediation string Runtime RuntimeDescription } func DescribeRuntime(options DescribeRuntimeOptions) (RuntimeDescription, error) { resolution, err := resolveManifestPolicy(OpenFromManifestOptions{ ServiceName: options.ServiceName, LookupEnv: options.LookupEnv, KWalletAppID: options.KWalletAppID, KWalletFolder: options.KWalletFolder, BitwardenCommand: options.BitwardenCommand, DisableBitwardenCache: options.DisableBitwardenCache, Shell: options.Shell, ManifestLoader: options.ManifestLoader, ExecutableResolver: options.ExecutableResolver, }) if err != nil { return RuntimeDescription{}, err } desc := RuntimeDescription{ ManifestSource: manifestSourceLabel(resolution.Source), DeclaredPolicy: resolution.Policy, EffectivePolicy: resolution.Policy, DisplayName: BackendDisplayName(resolution.Policy), } store, openErr := Open(Options{ ServiceName: options.ServiceName, BackendPolicy: resolution.Policy, LookupEnv: options.LookupEnv, KWalletAppID: options.KWalletAppID, KWalletFolder: options.KWalletFolder, BitwardenCommand: options.BitwardenCommand, BitwardenDebug: options.BitwardenDebug, DisableBitwardenCache: disableBitwardenCacheOption(options.DisableBitwardenCache, resolution.BitwardenCache), Shell: options.Shell, }) if openErr != nil { desc.Ready = false desc.ReadyError = openErr return desc, nil } desc.Ready = true if effective := EffectiveBackendPolicy(store); strings.TrimSpace(string(effective)) != "" { desc.EffectivePolicy = effective desc.DisplayName = BackendDisplayName(effective) } if options.CheckReady && desc.EffectivePolicy == BackendBitwardenCLI { if err := verifyBitwardenCLIReady(Options{ BitwardenCommand: options.BitwardenCommand, BitwardenDebug: options.BitwardenDebug, LookupEnv: options.LookupEnv, Shell: options.Shell, }); err != nil { desc.Ready = false desc.ReadyError = err } } return desc, nil } func PreflightFromManifest(options PreflightOptions) (PreflightReport, error) { options.CheckReady = true desc, err := DescribeRuntime(options) if err != nil { return PreflightReport{}, err } if desc.Ready { return PreflightReport{ Status: PreflightStatusReady, Summary: "secret backend is ready", Runtime: desc, }, nil } summary, remediation := summarizePreflightFailure(desc.ReadyError) return PreflightReport{ Status: PreflightStatusFail, Summary: summary, Remediation: remediation, Runtime: desc, }, nil } func BackendDisplayName(policy BackendPolicy) string { switch policy { case BackendBitwardenCLI: return "Bitwarden CLI" case BackendEnvOnly: return "Environment variables" case BackendKWalletOnly: return "KWallet" case BackendAuto: return "automatic backend selection" case BackendKeyringAny: return BackendName() default: trimmed := strings.TrimSpace(string(policy)) if trimmed == "" { return "unknown backend" } return trimmed } } func FormatBackendStatus(desc RuntimeDescription) string { source := manifestSourceLabel(desc.ManifestSource) display := strings.TrimSpace(desc.DisplayName) if display == "" { display = BackendDisplayName(desc.EffectivePolicy) } effective := desc.EffectivePolicy if strings.TrimSpace(string(effective)) == "" { effective = desc.DeclaredPolicy } parts := []string{ fmt.Sprintf("declared=%s", normalizeStatusPolicy(desc.DeclaredPolicy)), fmt.Sprintf("effective=%s", normalizeStatusPolicy(effective)), fmt.Sprintf("display=%s", display), fmt.Sprintf("ready=%t", desc.Ready), fmt.Sprintf("source=%s", source), } if desc.ReadyError != nil { parts = append(parts, fmt.Sprintf("error=%s", strings.TrimSpace(desc.ReadyError.Error()))) } return strings.Join(parts, " ") } func summarizePreflightFailure(err error) (string, string) { if err == nil { return "secret backend is unavailable", "" } switch { case errors.Is(err, ErrBWNotLoggedIn): return "bitwarden login is required", strings.TrimSpace(err.Error()) case errors.Is(err, ErrBWLocked): return "bitwarden vault is locked or BW_SESSION is missing", strings.TrimSpace(err.Error()) case errors.Is(err, ErrBWUnavailable): return "bitwarden CLI is unavailable", strings.TrimSpace(err.Error()) case errors.Is(err, ErrBackendUnavailable): return "secret backend is unavailable", strings.TrimSpace(err.Error()) default: return "secret backend preflight failed", strings.TrimSpace(err.Error()) } } func manifestSourceLabel(source string) string { trimmed := strings.TrimSpace(source) if trimmed == "" { return DefaultManifestSource } return trimmed } func normalizeStatusPolicy(policy BackendPolicy) string { trimmed := strings.TrimSpace(string(policy)) if trimmed == "" { return string(BackendAuto) } return trimmed }