Context
ADR-0027 gave per-layer staging (make root PROFILE=) but each layer root used
directory.recurse → all-or-nothing. A forker could not disable a swappable capability
(e.g. external-dns) without deleting Application YAML from git. We want one human-edited
config.yaml to drive which capabilities deploy, for a competent-k8s (not Argo-expert) audience,
without losing verbatim-YAML legibility.
Decision
Three orthogonal axes: staging (which layers, unchanged), selection (which capability groups exist, new), bring-up (manual-sync, unchanged).- Workloads stay verbatim
kind: ApplicationYAML inclusters/<env>/catalog/<group>/. scripts/resolve-groups.shturnsconfig.yaml features:intoclusters/<env>/groups.generated.yaml.- One ApplicationSet per layer (git-files generator + post-
selectoronenabled/layer) creates one app-of-apps per enabled group overcatalog/<group>(directory.recurse).
Capability groups
-core groups always on; optional groups default per the spec table. 13 groups across 6 layers
(platform, serving, routing, llm-gateway, experience, demos). experience is split out of demos
(real UX apps: open-webui/tabby/key-portal) vs sim/proof artifacts (demos).
Consequences
-
- One config file selects capabilities; disable = flip a bool, prune cascades.
-
- Workloads remain plain YAML; adding an app = drop a file in
catalog/<group>/.
- Workloads remain plain YAML; adding an app = drop a file in
- − Must keep catalog dirs free of Helm/Kustomize files (directory source renders plain YAML only).
- − Empty
groups.generated.yamlwould prune all groups →make doctor+make rootguard against it. - − Cross-app sync-wave gating not relied upon (children idempotent + retry); see ArgoCD #27917.
- Selection invariants + mitigations: see the design spec.
docs/superpowers/specs/2026-06-22-config-driven-feature-selection-design.md.