Files
member-console/internal/config/spec.go
T
cgalo5758 8e3c68c6be Make UI surfaces honestly reflect system state
- Add deployment-name branding to titles, mastheads, and OG tags
- Share one grant delivery-state query with lineage across grants
  surfaces
- Show pool status/usage, org owners, and config readiness
- Make billing views projection-aware with recency and sync vocabulary
- Guard FedWiki creation without domains and render route-aware 404s
2026-08-23 01:45:52 -05:00

114 lines
5.2 KiB
Go

package config
import "strings"
// ConfigKey declares one configuration key an integration owns — see
// design.md's "Declared config, spec-driven validation" decision
// (openspec/changes/integration-extraction/design.md). It is the single
// declaration cmd/start.go, this package's ValidateStart, and (once task
// 4.4 lands) internal/embeds/mc-config.yaml all derive their behavior
// from, replacing three previously hand-maintained, independently-drifting
// lists (cmd's flag/viper-default calls, validate.go's hardcoded
// per-integration conditional, and the YAML defaults file).
//
// Name is the viper/flag key (kebab-case, e.g. "stripe-api-key"). Default,
// when non-nil, is bound via viper.SetDefault; its dynamic type (string or
// []string today) tells cmd which cobra flag constructor to use. Usage is
// the flag's help text. Secret marks the key as sensitive: cmd also binds
// a "<name>-file" flag and, before validation runs, resolves a configured
// file path into Name (loading the value from disk) — mirroring the
// handling core's own secrets (csrf-secret, oidc-sp-client-secret, ...)
// already receive. RequiredGroup, when non-empty, ties this key to every
// other declared key sharing the same tag: ValidateStart rejects a
// configuration where some but not all of a group's keys are set (e.g.
// Stripe's stripe-api-key and stripe-webhook-secret share the tag
// "Stripe" — both must be set to enable billing, or both left empty to
// disable it).
type ConfigKey struct {
Name string
Default any
Usage string
Secret bool
RequiredGroup string
// Enum, when non-empty, closes the key's value set. The runtime
// settings surface renders such keys as a select and rejects
// non-members at save time; the boot overlay re-checks stored
// overrides. ValidateStart also checks every declared Enum key's
// resolved environment/flag/config-file value for membership (design
// D8, schema-hardening), sharing enumMember with CoerceOverride so the
// two paths cannot diverge.
Enum []string
}
// ConfigProvider is implemented by integrations that declare their own
// configuration keys, so the composition root (cmd/start.go) can bind
// flags/env/defaults and this package can validate presence and
// required-together groups without a hardcoded per-integration block. It
// is declared here — in internal/config, not internal/integrations —
// because this package is the one that actually consumes ConfigKey values
// (ValidateStart's required-group check) and cmd/start.go already imports
// it for that call; declaring ConfigKey/ConfigProvider here instead means
// internal/config never has to import internal/integrations (and,
// transitively, every concrete adapter's own dependencies — internal/
// server, internal/workflows/*, the Temporal client, net/http) merely to
// reference a plain declaration struct. This mirrors why server.
// RouteProvider and workflows.WorkflowProvider are declared in their own
// consuming packages rather than in internal/integrations.
type ConfigProvider interface {
ConfigSpec() []ConfigKey
}
// SecretPair names a secret configuration key and its "<name>-file"
// companion flag, as cmd binds them. SecretPairsFrom derives the list cmd
// resolves (load the file, if any, into Name) before validation runs, so
// that list is data-driven from declarations instead of a hand-maintained
// literal per integration.
type SecretPair struct {
Name string
FileName string
}
// SecretPairsFrom returns the {Name, "<Name>-file"} pair for every
// declared key with Secret set, in declaration order.
func SecretPairsFrom(keys []ConfigKey) []SecretPair {
var pairs []SecretPair
for _, k := range keys {
if k.Secret {
pairs = append(pairs, SecretPair{Name: k.Name, FileName: k.Name + "-file"})
}
}
return pairs
}
// RequiredKeysUnresolved returns the names, in declaration order, of every
// key whose RequiredGroup is non-empty (ValidateStart's "required
// together" keys — see validateRequiredGroups) and whose resolved value
// (environment/flag/config-file/default, read straight from viper — the
// same layer ValidateStart and the settings page itself read) is empty
// after trimming. A key with no RequiredGroup imposes no requirement and
// is never reported.
//
// This is the ONE resolution every honest-status surface shares (ux-
// honest-surfaces design decision 2): the Integrations list, the
// overview's system region, and the per-integration settings page all
// derive their configuration-readiness signal from this function, so
// "not configured" means the same thing everywhere it appears. Secret
// keys are covered too — they are still bound into viper by cmd/start.go
// (via the "<name>-file" resolution), only their *value* is withheld from
// display, never their presence.
func RequiredKeysUnresolved(keys []ConfigKey) []string {
var missing []string
for _, k := range keys {
if k.RequiredGroup == "" {
continue
}
// effectiveString (overlay.go) is the same per-key resolution
// ApplyOverlay and validateRequiredGroups already read; trimming
// matches validateRequiredGroups' own presence check exactly.
if strings.TrimSpace(effectiveString(k)) == "" {
missing = append(missing, k.Name)
}
}
return missing
}