Closes the otech113 dashboard regression where SovereigntyCard rendered
`invalid CutoverState: <undefined>` instead of a Tethered badge, and
makes the Day-2 cutover fire automatically once the chart lands rather
than waiting for an operator click on "Achieve True Sovereignty".
Founder rule per #933: handover is not "done" until cutover has run;
the operator must NOT have to click a CTA on
console.<sov-fqdn>/console/dashboard.
Three coupled changes:
1. catalyst-api: cutoverStatusResponse now ALWAYS emits a `state` field
("tethered" or "sovereign"), derived from cutoverComplete. The UI's
branded parseCutoverState rejects empty/undefined, which is what
was rendering the user-visible error text. Tests cover the empty
ConfigMap, missing cutoverComplete, and explicit-true cases.
2. UI parseCutoverStatus: defensive fallback when wire frame omits
`state` — derive from cutoverComplete (default "tethered"). Hostile/
typo'd state values (e.g. 'pending', '') still throw via the branded
parser. Defends against partial-rollout where a stale catalyst-api
Pod is still serving the old shape.
3. bp-self-sovereign-cutover 0.1.16 (chart): new Helm post-install/
post-upgrade hook (templates/10-auto-trigger-job.yaml) POSTs
/api/v1/sovereign/cutover/start on catalyst-api after the step
ConfigMaps + RBAC land. Idempotent via catalyst-api's durable
status ConfigMap (200 if already complete, 409 if running, 200
to start). Fails open: a transient catalyst-api unreachability
exits 0 so the chart install doesn't block; operator can always
re-fire via the manual CTA. Gated on .Values.trigger.auto (default
true; per-Sovereign overlays can disable for soak Sovereigns).
Hard rules honoured:
- No contabo Pods touched.
- Existing tethered Sovereigns that have not cutover stay tethered —
the auto-trigger Job is in the chart (per-Sovereign), not in the
mothership; only fresh Sovereign installs of bp-self-sovereign-cutover
0.1.16+ get it.
- IaC-first: the auto-trigger uses catalyst-api's existing /start
endpoint (no bespoke cluster mutation outside the chart).
- Event-driven: post-install hook fires on chart install (no cron).
Verification:
- Go: cutover_test.go +TestBuildCutoverStatusResponse_StateAlwaysDefined
+TestHandleCutoverStatus_StateFieldEmittedOnFreshSovereign — both
green.
- TS: cutover.test.ts +5 cases for parseCutoverStatus state-fallback;
35/35 green. Sovereignty widget tests 20/20 green.
- Chart: tests/cutover-contract.sh +Case 8/9 (auto-trigger present by
default, absent under trigger.auto=false); helm template renders
cleanly.
Co-authored-by: Hatice Yildiz <hatiyildiz@openova.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>