Group L closes the three UI smoke-test gaps the verify-sweep flagged:
#142 sovereign wizard — tests/e2e/playwright/tests/sovereign-wizard.spec.ts
#143 admin voucher UI — tests/e2e/playwright/tests/admin-vouchers.spec.ts
#144 unified bp-<x> grid — tests/e2e/playwright/tests/marketplace-cards.spec.ts
Tests target the actual shipped UI shape (Pass 105+):
* Wizard step model is StepOrg → StepTopology → StepProvider →
StepCredentials → StepComponents → StepReview, not the original ticket's
StepDomain/StepHetzner draft from before the unified-Blueprints refactor.
* Admin voucher model uses an `active` toggle, not ISSUED/REVOKED status.
* "Marketplace card grid" = the Catalyst wizard's StepComponents (bp-<x>
Blueprints), NOT the SME marketplace at core/marketplace (which is for
SaaS Apps). Today every Blueprint is `visibility: unlisted`, so the test
asserts the data layer (catalog.generated.ts) plus the documented
EmptyState; once `visibility: listed` lands, the third assertion
auto-extends to the rendered card grid.
Per principle #4 ("never hardcode"), all URLs come from env vars with
sensible local-dev defaults. Per principle #1 ("never speculate"), tests
self-skip with explicit reasons when their target app isn't reachable
instead of fail-noisy.
CI: .github/workflows/playwright-smoke.yaml boots the Catalyst UI in the
background and runs the suite on PRs touching UI sources or tests; admin
and marketplace specs self-skip in that workflow because spinning up all
three Astro apps + catalyst-api + Postgres is the full E2E pipeline's
job, not this smoke.
Local run (Catalyst UI on :4399, admin on :4398): 5 passed, 2 skipped
(skip reasons: marketplace #3 needs StepComponents reachable past
required-field gating; admin #2 needs ADMIN_TEST_COOKIE for an
authenticated session).
Refs: #142, #143, #144
Per docs/INVIOLABLE-PRINCIPLES.md #4 (never hardcode), the catalyst-ui
nginx config now flows from values.yaml at chart-render time:
- routing.basePath (/sovereign) — also drives ingress strip-prefix
- routing.catalystApi.serviceDNS — in-cluster reverse-proxy target
- routing.catalystApi.port — upstream port
- dns.resolverIP — CoreDNS for proxy-time resolution (avoids stale
ClusterIP after catalyst-api restarts)
- ingress.host / ingress.priority / ingress.className
Files:
- products/catalyst/chart/values.yaml — new, documents every default
- products/catalyst/chart/templates/ui-configmap.yaml — new, nginx
reverse-proxies /api/* to catalyst-api Service DNS
- products/catalyst/chart/templates/ui-deployment.yaml — mounts the
ConfigMap at /etc/nginx/conf.d/default.conf
- products/catalyst/chart/templates/ingress.yaml — values-driven host
+ path + priority + class
- tests/e2e/sovereign-routing/* — Playwright smoke for the routing
Captured from stalled agent /tmp/agent-sovereign-route-finish — agent
stream watchdog timed out after the work was authored but before commit.
Manual-dispatch-only DoD scaffolding for the omantel.omani.works
end-to-end test. Operator-gated; the test t.Skip()s when
HETZNER_TEST_TOKEN env var is missing so CI stays green.
- docs/DEMO-RUNBOOK.md: 9-step operator runbook covering Group C
cutover, wizard provision, voucher issuance, tenant redemption.
- tests/dod/dod_test.go: HTTP-driven E2E that streams SSE through
all 11 phases, asserts cert + DNS + voucher + redemption flow.
- .github/workflows/dod.yaml: workflow_dispatch only — never
on-push (Hetzner cost gating).
Cherry-picked additive files from /tmp/agent-group-m-dod (a40b495);
the agent's branch had stale-base deletions of #108/#109/Pass-107
that we drop.
Closes the Group L "end-to-end provisioning test on Hetzner test project"
ticket. Per the ticket's exact wording: scaffolding + harness + CI
workflow, gated on HETZNER_TEST_TOKEN, NEVER mocked.
Lifecycle when HETZNER_TEST_TOKEN is set:
1. Generate unique sovereign FQDN (e2e-<run-id>.openova.io)
2. Stage canonical infra/hetzner/ OpenTofu module into temp dir
3. Render tofu.auto.tfvars.json with test inputs (BYO domain mode so
Dynadot isn't touched; region runtime-configurable; SSH key minted
by CI per-run)
4. tofu init && tofu apply -auto-approve (30m timeout)
5. Assert outputs: control_plane_ip + load_balancer_ip are valid IPv4
6. Assert TCP/22 reachable on control plane (5m await)
7. Assert TCP/443 reachable on LB after Cilium + Flux land (15m await,
soft-failure since the Catalyst control plane install is the long
tail and partial-bootstrap is acceptable proof of OpenTofu + Flux)
8. tofu destroy -auto-approve (always — t.Cleanup, runs even on fail)
9. Verify state list is empty after destroy (no leaked resources)
When HETZNER_TEST_TOKEN is absent, the test SKIPS — does not mock, does
not fall through to a stub. Per docs/INVIOLABLE-PRINCIPLES.md #2,
mocking the cloud would tell us nothing about whether the OpenTofu module,
hcloud provider, cloud-init scripts, or k3s actually work. A second test
(TestHarness_NoHetznerCredsSkips) explicitly verifies the skip semantics
so future refactors don't accidentally land mocking.
CI workflow (.github/workflows/test-hetzner-e2e.yaml):
- Triggers on workflow_dispatch (operator initiates real run) or PR
labeled `test/hetzner-e2e` — NOT on every push (each run costs real
Hetzner minutes ~EUR 0.005/run).
- Generates a per-run throwaway SSH ed25519 keypair so no secret
long-term key lands in any logs.
- Installs OpenTofu via opentofu/setup-opentofu@v1.
- Reads HETZNER_TEST_TOKEN + HETZNER_TEST_PROJECT_ID from repo secrets;
operator populates them out-of-band (per the ticket: "operator will
populate later").
- 55m job timeout, plus the test itself uses contexts of 30m apply
+ 20m destroy.
Files:
- tests/e2e/hetzner-provisioning/main_test.go (the harness)
- tests/e2e/hetzner-provisioning/go.mod (separate module, stdlib-only)
- .github/workflows/test-hetzner-e2e.yaml (gated CI)
Refs #141
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the Group L "integration test — provisioner backend bootstrap-kit
installer — all 11 phases install in sequence on a kind cluster" ticket.
Per the ticket note, the bootstrap installer is now Flux-driven from
clusters/<sovereign-fqdn>/ — NOT the bespoke Go-based installer that was
reverted in commit e668637. The test verifies that Flux reconciles the
right Kustomizations rather than that Go code helm-installs anything.
Two layers of validation:
1. Static manifest layer (runs on every push, cheap)
- All 11 platform/<x>/blueprint.yaml + chart/Chart.yaml exist
- Each blueprint.yaml satisfies catalyst.openova.io/v1alpha1 schema
(apiVersion/kind/metadata.name/spec.version/card.title/card.summary)
- Chart.yaml name matches "bp-<x>" and version matches blueprint.yaml
spec.version
- clusters/_template/ YAMLs parse after SOVEREIGN_FQDN_PLACEHOLDER
substitution (when the template tree is on the branch — Group J/M
ticket lands the per-Sovereign template)
- The dependency order matches the canonical 11-phase sequence from
SOVEREIGN-PROVISIONING.md §3 (cilium → cert-manager → flux →
crossplane → sealed-secrets → spire → nats-jetstream → openbao →
keycloak → gitea → bp-catalyst-platform)
2. Kind-cluster layer (runs on main pushes, gated on
BOOTSTRAP_KIT_KIND_TEST=1)
- Brings up kubernetes-in-docker
- Installs Flux CRDs + source/kustomize controllers
- Registers a GitRepository pointing at this monorepo
- Synthesizes the 11 bootstrap-kit Kustomizations and applies them
- Asserts the API server accepts all 11 (manifests are valid, schema
satisfied) — this is the test's narrow scope per the ticket
The test deliberately does NOT wait for the kit to fully install upstream
charts or reach steady-state reconciliation. That belongs to #141 (real
Hetzner E2E with cloud credentials and outbound network), not a kind
cluster test in CI.
Files:
- tests/e2e/bootstrap-kit/main_test.go (Go test, 11 subtests + 4 main)
- tests/e2e/bootstrap-kit/go.mod (separate module — keeps test deps
isolated from the production Go modules)
- .github/workflows/test-bootstrap-kit.yaml (kind-action + flux2/action)
Refs #145
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>