Phase-2 follow-up to #883: replace mothership Stalwart relay
(mail.openova.io:587) with a Sovereign-local Stalwart so Console
PIN/magic-link mail originates from `noreply@<sovereignFQDN>` with
per-Sovereign SPF/DKIM/DMARC posture, eliminating the mothership
SMTP SPOF for Sovereign Console login.
What ships:
1. NEW blueprint platform/stalwart-sovereign/ (otech-level — distinct
from per-tenant bp-stalwart-tenant). Single Stalwart instance per
Sovereign cluster, scoped to Sovereign Console system mail. NO
Keycloak OIDC, NO webmail UI — Sovereign Console is the only
consumer. Auto-provisioned admin + submission Secrets via the
lookup-or-generate pattern (#898/#830/#887). Post-install Job:
- registers the noreply submission principal in Stalwart
- allows send-as for noreply@<sovereignFQDN>
- reads DKIM public key, patches dns-records ConfigMap
- materialises catalyst-system/sovereign-smtp-credentials with
Sovereign-local infrastructure addresses + credentials,
carrying BOTH key shapes (smtp-user/smtp-pass + legacy
user/password) so the consumer chart works either way.
2. NEW bootstrap-kit slot 95 (clusters/_template/bootstrap-kit/
95-bp-stalwart-sovereign.yaml). dependsOn: bp-cert-manager,
bp-catalyst-platform. Sequenced after bp-catalyst-platform (slot
13) so the chart's post-install Job lands its mirror Secret in
an already-existing catalyst-system namespace.
3. bp-catalyst-platform 1.4.19 → 1.4.20: SOURCE-wins precedence
extended to (a) non-secret fields smtp-host/smtp-port/smtp-from
so Sovereign-local infra addresses (`mail.<sovereignFQDN>`) take
over from mothership defaults (`mail.openova.io`) on the next
reconcile after slot 95 lands, and (b) canonical key shape
`smtp-user`/`smtp-pass` in addition to legacy `user`/`password`
source key shape.
4. expected-bootstrap-deps.yaml: declare slot 95 graph edge.
5. catalyst-api handler/sovereign_smtp_seed.go: documentation-only
update to note this Phase-1 step is now a graceful fallback —
the Phase-2 chart's post-install Job overwrites the mirror
Secret on first reconcile so the cutover from mothership relay
to Sovereign-local relay is automatic, no operator action.
Verification:
- `helm template smoke ./platform/stalwart-sovereign/chart` clean
(smoke-render-safe; per-template gates skip when sovereignFQDN unset).
- `helm template smoke -f operator-values.yaml` emits StatefulSet,
LoadBalancer Service, ClusterIP HTTP Service, DKIM-signing config,
dns-records ConfigMap, Setup Job + RBAC.
- `chart/tests/sovereign-render.sh` 3 cases all PASS.
- `helm template smoke ./products/catalyst/chart` (1.4.20) clean.
- `helm lint` both charts: clean (only icon-recommended INFO).
- `bash scripts/check-bootstrap-deps.sh` PASSED — bootstrap-kit
dependency graph audit, 0 drift, 0 cycles.
- `go test -run TestSeedSovereignSMTP` — Phase-1 seed tests pass.
- `go test -run TestBootstrapKit_TemplateClusterParses` — slot 95
YAML parses cleanly.
Out of scope (sub-PR follow-up under #924):
- DKIM keypair generation in catalyst-api orchestrator + DNS records
(MX/A/SPF/DMARC/DKIM-pubkey) registration via PDM dynadot adapter
at omani.works.
- Hetzner PTR (rDNS) auto-registration via the Hetzner cloud API.
- Cert-manager Certificate adding mail.<sovereignFQDN> SAN to the
Sovereign wildcard cert (chart relies on the existing wildcard
cert from bp-catalyst-platform 1.4.0+'s per-zone Certificate
template — when that wildcard chain covers the Sovereign FQDN,
`mail.<sovereignFQDN>` is already covered).
Acceptance (lands when sub-PR follow-up ships):
- Sovereign Console PIN delivery uses noreply@<sov-fqdn>.
- External mail server (e.g. Gmail) accepts mail with valid SPF + DKIM.
- Mothership SMTP no longer SPOF for Sovereign Console login.
Co-authored-by: hatiyildiz <hatiyildiz@openova.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>