Commit Graph

2168 Commits

Author SHA1 Message Date
github-actions[bot]
a65a024114 deploy: update catalyst images to c148ec6 2026-05-16 22:33:19 +00:00
e3mrah
c148ec6a34
fix(cloudinit): escape $$\{ORG_EMAIL:-\}/$$\{ORG_NAME:-\} in comment (D22) (#1575)
PR #1571 added a comment mentioning the $${ORG_EMAIL:-}/$${ORG_NAME:-}
slot-file placeholders WITHOUT the $$ escape. tofu's templatefile()
parses comments and tried to interpolate \${ORG_EMAIL:-} as a tofu
expression — failing with "Extra characters after interpolation
expression; Template interpolation doesn't expect a colon".

Caught live on t133 fad01d84f5655004 — tofu plan failed in 30s.

The escape pattern is documented at main.tf:1029 (the same warning
that caught t127 last week). $$ prefix tells tofu's templatefile to
emit literal \${...} to cloud-init for Flux envsubst.

Co-authored-by: hatiyildiz <hatice.yildiz@openova.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 02:31:26 +04:00
github-actions[bot]
c5f777056f deploy: update catalyst images to 3568b72 2026-05-16 22:20:19 +00:00
e3mrah
3568b72b5e
fix(cloud): hide non-active 0/0 chips (D15) (#1574)
* feat(chart): wire OPERATOR_EMAIL/CONTROL_PLANE_IP/GITOPS_REPO_URL/ORG_NAME (D22)

Companion to PR #1567 + #1568 — wire the env vars chrootEnsureDeployment
reads to populate the deployment record so Sovereign Console Settings
page renders real values for ownerEmail, controlPlaneIP, gitopsRepoURL,
orgName (instead of `—` placeholders).

Adds 4 new keys to the sovereign-fqdn ConfigMap (orgEmail, orgName,
controlPlaneIP, gitopsRepoURL) sourced from .Values.sovereign.* with
empty defaults. Per-Sovereign overlays wire actual values from cloud-
init substitute placeholders (mirrors regionsJson pattern).

Catalyst-api Pod now reads them via valueFrom configMapKeyRef +
optional=true (Catalyst-Zero/contabo emits no sovereign-fqdn ConfigMap
so env stays empty there — correct, mothership is signer not validator).

Validated: t132 already serves region=hel1, consoleURL, loadBalancerIP
post-#1568. This PR fills the remaining 3 D22 fields when operator wires
the values.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(slot-13): add D22 sovereign-side identity placeholders

Add ${ORG_EMAIL:-} + ${ORG_NAME:-} + ${SOVEREIGN_CONTROL_PLANE_IP:-} +
${GITOPS_REPO_URL:-} envsubst placeholders so when cloud-init wires
them, the chart picks them up via sovereign-fqdn ConfigMap (PR #1569)
→ catalyst-api env → chrootEnsureDeployment populates the deployment
record → Settings page renders real values instead of `—`.

This PR alone is a no-op (placeholders default to empty, same as today).
The cloud-init substitute lines + provisioner.go tfvars need to land in
a companion PR to actually populate the values on next-prov.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(cloudinit): wire ORG_EMAIL/ORG_NAME/GITOPS_REPO_URL substitutes (D22)

Companion to #1567+#1568+#1569+#1570 — the cloud-init substitute block
now emits ORG_EMAIL/ORG_NAME/GITOPS_REPO_URL into the bootstrap-kit
Kustomization's postBuild.substitute env, which the slot-13 placeholders
(#1570) consume via ${ORG_EMAIL:-}/${ORG_NAME:-}/${GITOPS_REPO_URL:-}.

Chain: provisioner.go writeTfvars → tofu vars → cloudinit templatefile
substitute → Flux Kustomization postBuild → sovereign-fqdn ConfigMap
keys (#1569) → catalyst-api env (#1569) → chrootEnsureDeployment
populates the deployment record (#1567 + #1568 fallback).

SOVEREIGN_CONTROL_PLANE_IP omitted intentionally — main.tf:691 notes
the dependency cycle (hcloud_server.cp doesn't exist at cloudinit
render time). Separate PR will source it via metadata-service or
post-create ConfigMap patch.

Next-prov (t133+) Sovereign Console Settings page now renders real
ownerEmail/orgName/gitopsRepoURL instead of `—` placeholders.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(router): chroot /app/<name> only-redirect mothership-only sub-paths (D17/D17b)

PR #1552 stripped the `/app` prefix on Sovereign mode to make
`/app/bp-cnpg` → `/bp-cnpg`, hoping consoleAppDetailRoute would match.
But consoleAppDetailRoute is registered at `/app/$componentId` under
consoleLayoutRoute — no chroot route matches `/<componentId>` directly,
so stripping leaves an empty render path. Playwright walkthrough on
t132 2026-05-17 confirmed: /app/bp-cnpg + /app/bp-coraza both render
body_len=9 (empty).

Invert the logic: only redirect mothership-only sub-paths (/dashboard
Fleet view, /install wizard, /sre, /sec, /blueprints) which have no
Sovereign Console equivalent. For everything else (component names like
`/app/bp-cnpg`, bare `/app`), let TanStack's natural most-specific-match
pick consoleAppDetailRoute / consoleAppsRoute.

Caught live on t132 via Playwright walker3.js — agent a4825c5a.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(handover): re-mint handover JWT on every GetDeployment (D0)

D0 Playwright walkthrough on t132 2026-05-17 caught: handoverURL
persisted at handover-fire time carries a JWT that expires per
DefaultTTL (5min). Operators who click /jobs hours later get the stale
token → Sovereign-side /auth/handover rejects with raw JSON
{"error":"invalid token"} — no UI fallback, no /auth/handover-error,
auto-redirect to /dashboard never fires.

Re-mint the JWT on every GetDeployment when deployment is ready +
handover-fired so the URL returned to the wizard is always
freshly-signed.

Best-effort: on mint failure, leave the existing URL in place so a
transient signer error doesn't break polling. Helper is idempotent +
locked.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(cloud): hide non-active 0/0 chips (D15)

Playwright walkthrough on t132 2026-05-17 caught D15 PARTIAL: 15 chips
are correct but Bucket+Volume show 0/0. Founder rule (DoD D15):
"No kind chip shows 0/0 for a resource that actually exists in the
cluster". Bucket+Volume genuinely don't exist on this Sovereign so
showing 0/0 is noise.

Hide chips with count exactly 0 unless they're the active selection
(operator who navigated to an empty kind keeps context).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: hatiyildiz <hatice.yildiz@openova.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 02:18:24 +04:00
github-actions[bot]
44e612f39d deploy: update catalyst images to 58dbb92 2026-05-16 22:18:16 +00:00
e3mrah
58dbb92f4f
fix(handover): re-mint handover JWT on every GetDeployment (D0) (#1573)
* feat(chart): wire OPERATOR_EMAIL/CONTROL_PLANE_IP/GITOPS_REPO_URL/ORG_NAME (D22)

Companion to PR #1567 + #1568 — wire the env vars chrootEnsureDeployment
reads to populate the deployment record so Sovereign Console Settings
page renders real values for ownerEmail, controlPlaneIP, gitopsRepoURL,
orgName (instead of `—` placeholders).

Adds 4 new keys to the sovereign-fqdn ConfigMap (orgEmail, orgName,
controlPlaneIP, gitopsRepoURL) sourced from .Values.sovereign.* with
empty defaults. Per-Sovereign overlays wire actual values from cloud-
init substitute placeholders (mirrors regionsJson pattern).

Catalyst-api Pod now reads them via valueFrom configMapKeyRef +
optional=true (Catalyst-Zero/contabo emits no sovereign-fqdn ConfigMap
so env stays empty there — correct, mothership is signer not validator).

Validated: t132 already serves region=hel1, consoleURL, loadBalancerIP
post-#1568. This PR fills the remaining 3 D22 fields when operator wires
the values.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(slot-13): add D22 sovereign-side identity placeholders

Add ${ORG_EMAIL:-} + ${ORG_NAME:-} + ${SOVEREIGN_CONTROL_PLANE_IP:-} +
${GITOPS_REPO_URL:-} envsubst placeholders so when cloud-init wires
them, the chart picks them up via sovereign-fqdn ConfigMap (PR #1569)
→ catalyst-api env → chrootEnsureDeployment populates the deployment
record → Settings page renders real values instead of `—`.

This PR alone is a no-op (placeholders default to empty, same as today).
The cloud-init substitute lines + provisioner.go tfvars need to land in
a companion PR to actually populate the values on next-prov.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(cloudinit): wire ORG_EMAIL/ORG_NAME/GITOPS_REPO_URL substitutes (D22)

Companion to #1567+#1568+#1569+#1570 — the cloud-init substitute block
now emits ORG_EMAIL/ORG_NAME/GITOPS_REPO_URL into the bootstrap-kit
Kustomization's postBuild.substitute env, which the slot-13 placeholders
(#1570) consume via ${ORG_EMAIL:-}/${ORG_NAME:-}/${GITOPS_REPO_URL:-}.

Chain: provisioner.go writeTfvars → tofu vars → cloudinit templatefile
substitute → Flux Kustomization postBuild → sovereign-fqdn ConfigMap
keys (#1569) → catalyst-api env (#1569) → chrootEnsureDeployment
populates the deployment record (#1567 + #1568 fallback).

SOVEREIGN_CONTROL_PLANE_IP omitted intentionally — main.tf:691 notes
the dependency cycle (hcloud_server.cp doesn't exist at cloudinit
render time). Separate PR will source it via metadata-service or
post-create ConfigMap patch.

Next-prov (t133+) Sovereign Console Settings page now renders real
ownerEmail/orgName/gitopsRepoURL instead of `—` placeholders.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(router): chroot /app/<name> only-redirect mothership-only sub-paths (D17/D17b)

PR #1552 stripped the `/app` prefix on Sovereign mode to make
`/app/bp-cnpg` → `/bp-cnpg`, hoping consoleAppDetailRoute would match.
But consoleAppDetailRoute is registered at `/app/$componentId` under
consoleLayoutRoute — no chroot route matches `/<componentId>` directly,
so stripping leaves an empty render path. Playwright walkthrough on
t132 2026-05-17 confirmed: /app/bp-cnpg + /app/bp-coraza both render
body_len=9 (empty).

Invert the logic: only redirect mothership-only sub-paths (/dashboard
Fleet view, /install wizard, /sre, /sec, /blueprints) which have no
Sovereign Console equivalent. For everything else (component names like
`/app/bp-cnpg`, bare `/app`), let TanStack's natural most-specific-match
pick consoleAppDetailRoute / consoleAppsRoute.

Caught live on t132 via Playwright walker3.js — agent a4825c5a.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(handover): re-mint handover JWT on every GetDeployment (D0)

D0 Playwright walkthrough on t132 2026-05-17 caught: handoverURL
persisted at handover-fire time carries a JWT that expires per
DefaultTTL (5min). Operators who click /jobs hours later get the stale
token → Sovereign-side /auth/handover rejects with raw JSON
{"error":"invalid token"} — no UI fallback, no /auth/handover-error,
auto-redirect to /dashboard never fires.

Re-mint the JWT on every GetDeployment when deployment is ready +
handover-fired so the URL returned to the wizard is always
freshly-signed.

Best-effort: on mint failure, leave the existing URL in place so a
transient signer error doesn't break polling. Helper is idempotent +
locked.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: hatiyildiz <hatice.yildiz@openova.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 02:16:26 +04:00
github-actions[bot]
dea683f5e4 deploy: update catalyst images to 9e1e422 2026-05-16 22:08:01 +00:00
e3mrah
9e1e4224d8
fix(router): chroot /app/<name> only-redirect mothership-only sub-paths (D17/D17b) (#1572)
* feat(chart): wire OPERATOR_EMAIL/CONTROL_PLANE_IP/GITOPS_REPO_URL/ORG_NAME (D22)

Companion to PR #1567 + #1568 — wire the env vars chrootEnsureDeployment
reads to populate the deployment record so Sovereign Console Settings
page renders real values for ownerEmail, controlPlaneIP, gitopsRepoURL,
orgName (instead of `—` placeholders).

Adds 4 new keys to the sovereign-fqdn ConfigMap (orgEmail, orgName,
controlPlaneIP, gitopsRepoURL) sourced from .Values.sovereign.* with
empty defaults. Per-Sovereign overlays wire actual values from cloud-
init substitute placeholders (mirrors regionsJson pattern).

Catalyst-api Pod now reads them via valueFrom configMapKeyRef +
optional=true (Catalyst-Zero/contabo emits no sovereign-fqdn ConfigMap
so env stays empty there — correct, mothership is signer not validator).

Validated: t132 already serves region=hel1, consoleURL, loadBalancerIP
post-#1568. This PR fills the remaining 3 D22 fields when operator wires
the values.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(slot-13): add D22 sovereign-side identity placeholders

Add ${ORG_EMAIL:-} + ${ORG_NAME:-} + ${SOVEREIGN_CONTROL_PLANE_IP:-} +
${GITOPS_REPO_URL:-} envsubst placeholders so when cloud-init wires
them, the chart picks them up via sovereign-fqdn ConfigMap (PR #1569)
→ catalyst-api env → chrootEnsureDeployment populates the deployment
record → Settings page renders real values instead of `—`.

This PR alone is a no-op (placeholders default to empty, same as today).
The cloud-init substitute lines + provisioner.go tfvars need to land in
a companion PR to actually populate the values on next-prov.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(cloudinit): wire ORG_EMAIL/ORG_NAME/GITOPS_REPO_URL substitutes (D22)

Companion to #1567+#1568+#1569+#1570 — the cloud-init substitute block
now emits ORG_EMAIL/ORG_NAME/GITOPS_REPO_URL into the bootstrap-kit
Kustomization's postBuild.substitute env, which the slot-13 placeholders
(#1570) consume via ${ORG_EMAIL:-}/${ORG_NAME:-}/${GITOPS_REPO_URL:-}.

Chain: provisioner.go writeTfvars → tofu vars → cloudinit templatefile
substitute → Flux Kustomization postBuild → sovereign-fqdn ConfigMap
keys (#1569) → catalyst-api env (#1569) → chrootEnsureDeployment
populates the deployment record (#1567 + #1568 fallback).

SOVEREIGN_CONTROL_PLANE_IP omitted intentionally — main.tf:691 notes
the dependency cycle (hcloud_server.cp doesn't exist at cloudinit
render time). Separate PR will source it via metadata-service or
post-create ConfigMap patch.

Next-prov (t133+) Sovereign Console Settings page now renders real
ownerEmail/orgName/gitopsRepoURL instead of `—` placeholders.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(router): chroot /app/<name> only-redirect mothership-only sub-paths (D17/D17b)

PR #1552 stripped the `/app` prefix on Sovereign mode to make
`/app/bp-cnpg` → `/bp-cnpg`, hoping consoleAppDetailRoute would match.
But consoleAppDetailRoute is registered at `/app/$componentId` under
consoleLayoutRoute — no chroot route matches `/<componentId>` directly,
so stripping leaves an empty render path. Playwright walkthrough on
t132 2026-05-17 confirmed: /app/bp-cnpg + /app/bp-coraza both render
body_len=9 (empty).

Invert the logic: only redirect mothership-only sub-paths (/dashboard
Fleet view, /install wizard, /sre, /sec, /blueprints) which have no
Sovereign Console equivalent. For everything else (component names like
`/app/bp-cnpg`, bare `/app`), let TanStack's natural most-specific-match
pick consoleAppDetailRoute / consoleAppsRoute.

Caught live on t132 via Playwright walker3.js — agent a4825c5a.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: hatiyildiz <hatice.yildiz@openova.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 02:05:54 +04:00
github-actions[bot]
4cc880cafd deploy: update catalyst images to 5793958 2026-05-16 21:48:54 +00:00
e3mrah
57939585c0
feat(cloudinit): wire ORG_EMAIL/ORG_NAME/GITOPS_REPO_URL substitutes (D22) (#1571)
* feat(chart): wire OPERATOR_EMAIL/CONTROL_PLANE_IP/GITOPS_REPO_URL/ORG_NAME (D22)

Companion to PR #1567 + #1568 — wire the env vars chrootEnsureDeployment
reads to populate the deployment record so Sovereign Console Settings
page renders real values for ownerEmail, controlPlaneIP, gitopsRepoURL,
orgName (instead of `—` placeholders).

Adds 4 new keys to the sovereign-fqdn ConfigMap (orgEmail, orgName,
controlPlaneIP, gitopsRepoURL) sourced from .Values.sovereign.* with
empty defaults. Per-Sovereign overlays wire actual values from cloud-
init substitute placeholders (mirrors regionsJson pattern).

Catalyst-api Pod now reads them via valueFrom configMapKeyRef +
optional=true (Catalyst-Zero/contabo emits no sovereign-fqdn ConfigMap
so env stays empty there — correct, mothership is signer not validator).

Validated: t132 already serves region=hel1, consoleURL, loadBalancerIP
post-#1568. This PR fills the remaining 3 D22 fields when operator wires
the values.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(slot-13): add D22 sovereign-side identity placeholders

Add ${ORG_EMAIL:-} + ${ORG_NAME:-} + ${SOVEREIGN_CONTROL_PLANE_IP:-} +
${GITOPS_REPO_URL:-} envsubst placeholders so when cloud-init wires
them, the chart picks them up via sovereign-fqdn ConfigMap (PR #1569)
→ catalyst-api env → chrootEnsureDeployment populates the deployment
record → Settings page renders real values instead of `—`.

This PR alone is a no-op (placeholders default to empty, same as today).
The cloud-init substitute lines + provisioner.go tfvars need to land in
a companion PR to actually populate the values on next-prov.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(cloudinit): wire ORG_EMAIL/ORG_NAME/GITOPS_REPO_URL substitutes (D22)

Companion to #1567+#1568+#1569+#1570 — the cloud-init substitute block
now emits ORG_EMAIL/ORG_NAME/GITOPS_REPO_URL into the bootstrap-kit
Kustomization's postBuild.substitute env, which the slot-13 placeholders
(#1570) consume via ${ORG_EMAIL:-}/${ORG_NAME:-}/${GITOPS_REPO_URL:-}.

Chain: provisioner.go writeTfvars → tofu vars → cloudinit templatefile
substitute → Flux Kustomization postBuild → sovereign-fqdn ConfigMap
keys (#1569) → catalyst-api env (#1569) → chrootEnsureDeployment
populates the deployment record (#1567 + #1568 fallback).

SOVEREIGN_CONTROL_PLANE_IP omitted intentionally — main.tf:691 notes
the dependency cycle (hcloud_server.cp doesn't exist at cloudinit
render time). Separate PR will source it via metadata-service or
post-create ConfigMap patch.

Next-prov (t133+) Sovereign Console Settings page now renders real
ownerEmail/orgName/gitopsRepoURL instead of `—` placeholders.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: hatiyildiz <hatice.yildiz@openova.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 01:47:04 +04:00
e3mrah
700d28967f
chore(slot-13): add D22 sovereign-side identity placeholders (#1570)
* feat(chart): wire OPERATOR_EMAIL/CONTROL_PLANE_IP/GITOPS_REPO_URL/ORG_NAME (D22)

Companion to PR #1567 + #1568 — wire the env vars chrootEnsureDeployment
reads to populate the deployment record so Sovereign Console Settings
page renders real values for ownerEmail, controlPlaneIP, gitopsRepoURL,
orgName (instead of `—` placeholders).

Adds 4 new keys to the sovereign-fqdn ConfigMap (orgEmail, orgName,
controlPlaneIP, gitopsRepoURL) sourced from .Values.sovereign.* with
empty defaults. Per-Sovereign overlays wire actual values from cloud-
init substitute placeholders (mirrors regionsJson pattern).

Catalyst-api Pod now reads them via valueFrom configMapKeyRef +
optional=true (Catalyst-Zero/contabo emits no sovereign-fqdn ConfigMap
so env stays empty there — correct, mothership is signer not validator).

Validated: t132 already serves region=hel1, consoleURL, loadBalancerIP
post-#1568. This PR fills the remaining 3 D22 fields when operator wires
the values.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(slot-13): add D22 sovereign-side identity placeholders

Add ${ORG_EMAIL:-} + ${ORG_NAME:-} + ${SOVEREIGN_CONTROL_PLANE_IP:-} +
${GITOPS_REPO_URL:-} envsubst placeholders so when cloud-init wires
them, the chart picks them up via sovereign-fqdn ConfigMap (PR #1569)
→ catalyst-api env → chrootEnsureDeployment populates the deployment
record → Settings page renders real values instead of `—`.

This PR alone is a no-op (placeholders default to empty, same as today).
The cloud-init substitute lines + provisioner.go tfvars need to land in
a companion PR to actually populate the values on next-prov.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: hatiyildiz <hatice.yildiz@openova.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 01:29:59 +04:00
github-actions[bot]
df193d340e deploy: update catalyst images to 9cbcd23 2026-05-16 21:03:01 +00:00
e3mrah
9cbcd230da
feat(chart): wire OPERATOR_EMAIL/CONTROL_PLANE_IP/GITOPS_REPO_URL/ORG_NAME (D22) (#1569)
Companion to PR #1567 + #1568 — wire the env vars chrootEnsureDeployment
reads to populate the deployment record so Sovereign Console Settings
page renders real values for ownerEmail, controlPlaneIP, gitopsRepoURL,
orgName (instead of `—` placeholders).

Adds 4 new keys to the sovereign-fqdn ConfigMap (orgEmail, orgName,
controlPlaneIP, gitopsRepoURL) sourced from .Values.sovereign.* with
empty defaults. Per-Sovereign overlays wire actual values from cloud-
init substitute placeholders (mirrors regionsJson pattern).

Catalyst-api Pod now reads them via valueFrom configMapKeyRef +
optional=true (Catalyst-Zero/contabo emits no sovereign-fqdn ConfigMap
so env stays empty there — correct, mothership is signer not validator).

Validated: t132 already serves region=hel1, consoleURL, loadBalancerIP
post-#1568. This PR fills the remaining 3 D22 fields when operator wires
the values.

Co-authored-by: hatiyildiz <hatice.yildiz@openova.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 01:01:00 +04:00
github-actions[bot]
0e0280bbe0 deploy: update catalyst images to 6618392 2026-05-16 20:56:10 +00:00
e3mrah
6618392407
fix(chroot): GetDeployment falls back to chrootEnsureDeployment (D22) (#1568)
* feat(handover): auto-seed owner UserAccess CR on chroot (D21)

Closes the D21 gap on Sovereign DoD: /users page returned empty after
fresh handover because Keycloak `sovereign-admins` membership was
established but no UserAccess CR existed for the operator.

After `keycloak.EnsureUser` succeeds in `AuthHandover`, the helper
`EnsureOwnerUserAccess` upserts a cluster-scoped UserAccess CR shaped
like the canonical user_access.go `CreateUserAccess` write:

  apiVersion: access.openova.io/v1alpha1
  kind: UserAccess
  metadata:
    name: useraccess-owner-<sanitized-email>
    annotations:
      catalyst.openova.io/user-email: <email>   # rbac_matrix:309 hint
  spec:
    user:
      keycloakSubject: <email>
    sovereignRef: <fqdn-first-label>
    applications:
      - app: "*"
        role: admin                              # owner -> admin

The Composition (issue #322) reconciles the Claim into per-app
RoleBindings on the Sovereign so the operator surfaces in /users.

Best-effort + idempotent: AlreadyExists on the second handover is
folded to nil; any other error is logged at Warn and the handover
itself never fails. If the access.openova.io CRD has not rolled yet,
the next handover retries automatically.

Architect-first: mirrors `userAccessToUnstructured` shape and uses
existing `sovereignDynamicClient` + `rbacAssignSlug` seams. Tier
mapping follows the documented lossy `owner -> admin` rule in
`userAccessTierToRole` (CRD only accepts admin|editor|viewer).

Refs: docs/SOVEREIGN-MULTI-REGION-DOD.md D21

* chore(slot-13): pin bp-catalyst-platform to 1.4.147 (D21+D31 baked)

PR #1562 (D31 wordpress-tenant activeHotStandby) + PR #1564 (D21 owner
UserAccess auto-seed at handover, catalyst-api:8d2a947) both packaged
into chart 1.4.147. Pin slot so t133+ gets both gates on first prov.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(chart): regionsJson uses toJson to defeat YAML flow-seq re-parse (D5)

PR #1551 single-quoted SOVEREIGN_REGIONS_JSON in the slot file
substitute, but Flux Kustomize's postBuild can still re-parse the
JSON-shaped string as a YAML flow-sequence depending on quoting context.
When that happens .Values.sovereign.regionsJson is a Go []interface{}
of map[interface{}]interface{} and `| quote` prints Go's
`[map[cloudRegion:hel1 ...]]` syntax — catalyst-api's json.Unmarshal of
the env var then fails and Request.Regions is empty.

toJson normalises both string and list inputs to valid JSON.

Caught live on t132 2026-05-16 chart 1.4.147: env var rendered as
`[map[cloudRegion:hel1 ...]]` despite #1551 being in effect.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(chroot): populate deployment Result + Request fields for D22

Settings page on Sovereign Console renders `—` for Region / Sovereign /
Created / DeploymentID / Pool subdomain because chroot's GET
/api/v1/deployments/<id> returns empty strings for those fields.

Populate from existing env vars (best-effort — empty when chart hasn't
wired them yet, which is no worse than today's behaviour):
- Result.ConsoleURL = "https://console.<fqdn>" (derived from selfFQDN)
- Result.GitOpsRepoURL from GITOPS_REPO_URL env
- Result.ControlPlaneIP from SOVEREIGN_CONTROL_PLANE_IP env
- Request.Region = regions[0].CloudRegion (top-level legacy field)
- Request.OrgEmail from OPERATOR_EMAIL env
- Request.OrgName from ORG_NAME env

Companion chart PR will wire the env vars from .Values.global.* +
cloud-init substitute placeholders. This PR is BACKWARD-compatible —
unset env vars produce empty strings, same as today.

Caught live on t132 2026-05-16 — `curl /api/v1/deployments/sovereign-
t132.omani.works` returns empty ownerEmail/region/consoleURL.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(chroot): GetDeployment falls back to chrootEnsureDeployment (D22)

GetDeployment was the only handler that returned 404 without calling
chrootEnsureDeployment. After a catalyst-api Pod restart on the chroot
the in-memory store is empty until some other handler (StreamLogs,
jobs list) primes it via its own synth call — meanwhile the Sovereign
Console Settings page loads /api/v1/deployments/<id> first and gets
404, rendering the entire page broken.

Mirror the StreamLogs pattern (lines 1247-1254): try in-memory load,
fall through to chrootEnsureDeployment, return 404 only when both miss.

This unblocks PR #1567's deployment-record population — without the
fallback, GetDeployment can never serve the populated record on chroot.

Caught live on t132 2026-05-16 after #1567 image roll: Settings page
404 because in-memory store was empty.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: hatiyildiz <hatice.yildiz@openova.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 00:54:20 +04:00
github-actions[bot]
b094a354b7 deploy: update catalyst images to ed63ecd 2026-05-16 20:31:39 +00:00
e3mrah
ed63ecd09f
fix(chroot): populate deployment Result + Request fields for D22 settings (#1567)
* feat(handover): auto-seed owner UserAccess CR on chroot (D21)

Closes the D21 gap on Sovereign DoD: /users page returned empty after
fresh handover because Keycloak `sovereign-admins` membership was
established but no UserAccess CR existed for the operator.

After `keycloak.EnsureUser` succeeds in `AuthHandover`, the helper
`EnsureOwnerUserAccess` upserts a cluster-scoped UserAccess CR shaped
like the canonical user_access.go `CreateUserAccess` write:

  apiVersion: access.openova.io/v1alpha1
  kind: UserAccess
  metadata:
    name: useraccess-owner-<sanitized-email>
    annotations:
      catalyst.openova.io/user-email: <email>   # rbac_matrix:309 hint
  spec:
    user:
      keycloakSubject: <email>
    sovereignRef: <fqdn-first-label>
    applications:
      - app: "*"
        role: admin                              # owner -> admin

The Composition (issue #322) reconciles the Claim into per-app
RoleBindings on the Sovereign so the operator surfaces in /users.

Best-effort + idempotent: AlreadyExists on the second handover is
folded to nil; any other error is logged at Warn and the handover
itself never fails. If the access.openova.io CRD has not rolled yet,
the next handover retries automatically.

Architect-first: mirrors `userAccessToUnstructured` shape and uses
existing `sovereignDynamicClient` + `rbacAssignSlug` seams. Tier
mapping follows the documented lossy `owner -> admin` rule in
`userAccessTierToRole` (CRD only accepts admin|editor|viewer).

Refs: docs/SOVEREIGN-MULTI-REGION-DOD.md D21

* chore(slot-13): pin bp-catalyst-platform to 1.4.147 (D21+D31 baked)

PR #1562 (D31 wordpress-tenant activeHotStandby) + PR #1564 (D21 owner
UserAccess auto-seed at handover, catalyst-api:8d2a947) both packaged
into chart 1.4.147. Pin slot so t133+ gets both gates on first prov.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(chart): regionsJson uses toJson to defeat YAML flow-seq re-parse (D5)

PR #1551 single-quoted SOVEREIGN_REGIONS_JSON in the slot file
substitute, but Flux Kustomize's postBuild can still re-parse the
JSON-shaped string as a YAML flow-sequence depending on quoting context.
When that happens .Values.sovereign.regionsJson is a Go []interface{}
of map[interface{}]interface{} and `| quote` prints Go's
`[map[cloudRegion:hel1 ...]]` syntax — catalyst-api's json.Unmarshal of
the env var then fails and Request.Regions is empty.

toJson normalises both string and list inputs to valid JSON.

Caught live on t132 2026-05-16 chart 1.4.147: env var rendered as
`[map[cloudRegion:hel1 ...]]` despite #1551 being in effect.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(chroot): populate deployment Result + Request fields for D22

Settings page on Sovereign Console renders `—` for Region / Sovereign /
Created / DeploymentID / Pool subdomain because chroot's GET
/api/v1/deployments/<id> returns empty strings for those fields.

Populate from existing env vars (best-effort — empty when chart hasn't
wired them yet, which is no worse than today's behaviour):
- Result.ConsoleURL = "https://console.<fqdn>" (derived from selfFQDN)
- Result.GitOpsRepoURL from GITOPS_REPO_URL env
- Result.ControlPlaneIP from SOVEREIGN_CONTROL_PLANE_IP env
- Request.Region = regions[0].CloudRegion (top-level legacy field)
- Request.OrgEmail from OPERATOR_EMAIL env
- Request.OrgName from ORG_NAME env

Companion chart PR will wire the env vars from .Values.global.* +
cloud-init substitute placeholders. This PR is BACKWARD-compatible —
unset env vars produce empty strings, same as today.

Caught live on t132 2026-05-16 — `curl /api/v1/deployments/sovereign-
t132.omani.works` returns empty ownerEmail/region/consoleURL.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: hatiyildiz <hatice.yildiz@openova.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 00:29:44 +04:00
github-actions[bot]
d82e06bfe9 deploy: update catalyst images to 0a45fb0 2026-05-16 20:03:41 +00:00
e3mrah
0a45fb0449
fix(chart): regionsJson uses toJson to defeat YAML flow-seq re-parse (D5) (#1566)
* feat(handover): auto-seed owner UserAccess CR on chroot (D21)

Closes the D21 gap on Sovereign DoD: /users page returned empty after
fresh handover because Keycloak `sovereign-admins` membership was
established but no UserAccess CR existed for the operator.

After `keycloak.EnsureUser` succeeds in `AuthHandover`, the helper
`EnsureOwnerUserAccess` upserts a cluster-scoped UserAccess CR shaped
like the canonical user_access.go `CreateUserAccess` write:

  apiVersion: access.openova.io/v1alpha1
  kind: UserAccess
  metadata:
    name: useraccess-owner-<sanitized-email>
    annotations:
      catalyst.openova.io/user-email: <email>   # rbac_matrix:309 hint
  spec:
    user:
      keycloakSubject: <email>
    sovereignRef: <fqdn-first-label>
    applications:
      - app: "*"
        role: admin                              # owner -> admin

The Composition (issue #322) reconciles the Claim into per-app
RoleBindings on the Sovereign so the operator surfaces in /users.

Best-effort + idempotent: AlreadyExists on the second handover is
folded to nil; any other error is logged at Warn and the handover
itself never fails. If the access.openova.io CRD has not rolled yet,
the next handover retries automatically.

Architect-first: mirrors `userAccessToUnstructured` shape and uses
existing `sovereignDynamicClient` + `rbacAssignSlug` seams. Tier
mapping follows the documented lossy `owner -> admin` rule in
`userAccessTierToRole` (CRD only accepts admin|editor|viewer).

Refs: docs/SOVEREIGN-MULTI-REGION-DOD.md D21

* chore(slot-13): pin bp-catalyst-platform to 1.4.147 (D21+D31 baked)

PR #1562 (D31 wordpress-tenant activeHotStandby) + PR #1564 (D21 owner
UserAccess auto-seed at handover, catalyst-api:8d2a947) both packaged
into chart 1.4.147. Pin slot so t133+ gets both gates on first prov.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(chart): regionsJson uses toJson to defeat YAML flow-seq re-parse (D5)

PR #1551 single-quoted SOVEREIGN_REGIONS_JSON in the slot file
substitute, but Flux Kustomize's postBuild can still re-parse the
JSON-shaped string as a YAML flow-sequence depending on quoting context.
When that happens .Values.sovereign.regionsJson is a Go []interface{}
of map[interface{}]interface{} and `| quote` prints Go's
`[map[cloudRegion:hel1 ...]]` syntax — catalyst-api's json.Unmarshal of
the env var then fails and Request.Regions is empty.

toJson normalises both string and list inputs to valid JSON.

Caught live on t132 2026-05-16 chart 1.4.147: env var rendered as
`[map[cloudRegion:hel1 ...]]` despite #1551 being in effect.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: hatiyildiz <hatice.yildiz@openova.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 00:01:43 +04:00
e3mrah
3f8e2b925e
chore(slot-13): pin bp-catalyst-platform to 1.4.147 (D21+D31 baked) (#1565)
* feat(handover): auto-seed owner UserAccess CR on chroot (D21)

Closes the D21 gap on Sovereign DoD: /users page returned empty after
fresh handover because Keycloak `sovereign-admins` membership was
established but no UserAccess CR existed for the operator.

After `keycloak.EnsureUser` succeeds in `AuthHandover`, the helper
`EnsureOwnerUserAccess` upserts a cluster-scoped UserAccess CR shaped
like the canonical user_access.go `CreateUserAccess` write:

  apiVersion: access.openova.io/v1alpha1
  kind: UserAccess
  metadata:
    name: useraccess-owner-<sanitized-email>
    annotations:
      catalyst.openova.io/user-email: <email>   # rbac_matrix:309 hint
  spec:
    user:
      keycloakSubject: <email>
    sovereignRef: <fqdn-first-label>
    applications:
      - app: "*"
        role: admin                              # owner -> admin

The Composition (issue #322) reconciles the Claim into per-app
RoleBindings on the Sovereign so the operator surfaces in /users.

Best-effort + idempotent: AlreadyExists on the second handover is
folded to nil; any other error is logged at Warn and the handover
itself never fails. If the access.openova.io CRD has not rolled yet,
the next handover retries automatically.

Architect-first: mirrors `userAccessToUnstructured` shape and uses
existing `sovereignDynamicClient` + `rbacAssignSlug` seams. Tier
mapping follows the documented lossy `owner -> admin` rule in
`userAccessTierToRole` (CRD only accepts admin|editor|viewer).

Refs: docs/SOVEREIGN-MULTI-REGION-DOD.md D21

* chore(slot-13): pin bp-catalyst-platform to 1.4.147 (D21+D31 baked)

PR #1562 (D31 wordpress-tenant activeHotStandby) + PR #1564 (D21 owner
UserAccess auto-seed at handover, catalyst-api:8d2a947) both packaged
into chart 1.4.147. Pin slot so t133+ gets both gates on first prov.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: hatiyildiz <hatice.yildiz@openova.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 23:58:46 +04:00
github-actions[bot]
f8c8a87151 deploy: update catalyst images to 8d2a947 2026-05-16 19:51:40 +00:00
e3mrah
8d2a947cfb
feat(handover): auto-seed owner UserAccess CR on chroot (D21) (#1564)
Closes the D21 gap on Sovereign DoD: /users page returned empty after
fresh handover because Keycloak `sovereign-admins` membership was
established but no UserAccess CR existed for the operator.

After `keycloak.EnsureUser` succeeds in `AuthHandover`, the helper
`EnsureOwnerUserAccess` upserts a cluster-scoped UserAccess CR shaped
like the canonical user_access.go `CreateUserAccess` write:

  apiVersion: access.openova.io/v1alpha1
  kind: UserAccess
  metadata:
    name: useraccess-owner-<sanitized-email>
    annotations:
      catalyst.openova.io/user-email: <email>   # rbac_matrix:309 hint
  spec:
    user:
      keycloakSubject: <email>
    sovereignRef: <fqdn-first-label>
    applications:
      - app: "*"
        role: admin                              # owner -> admin

The Composition (issue #322) reconciles the Claim into per-app
RoleBindings on the Sovereign so the operator surfaces in /users.

Best-effort + idempotent: AlreadyExists on the second handover is
folded to nil; any other error is logged at Warn and the handover
itself never fails. If the access.openova.io CRD has not rolled yet,
the next handover retries automatically.

Architect-first: mirrors `userAccessToUnstructured` shape and uses
existing `sovereignDynamicClient` + `rbacAssignSlug` seams. Tier
mapping follows the documented lossy `owner -> admin` rule in
`userAccessTierToRole` (CRD only accepts admin|editor|viewer).

Refs: docs/SOVEREIGN-MULTI-REGION-DOD.md D21

Co-authored-by: hatiyildiz <hatice.yildiz@openova.io>
2026-05-16 23:49:32 +04:00
e3mrah
5510ab91f9
chore(slot-13): pin bp-catalyst-platform to 1.4.146 (D29 billing JWT bypass) (#1563)
PR #1561 added billing-service JWT exemptions matching gateway public
routes (D29 voucher-redeem zero-touch). Pin slot so future provisions
inherit the full D29 unblocker chain.

Co-authored-by: hatiyildiz <hatice.yildiz@openova.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 23:41:34 +04:00
github-actions[bot]
d6b6aca581 deploy: update sme service images to c04b2ec + bump chart to 1.4.147 2026-05-16 19:41:18 +00:00
e3mrah
c04b2ec76d
feat(wordpress-tenant): activeHotStandby option wires bp-cnpg-pair (D31) (#1562)
Sovereign DoD D31 — tenants subscribing to an HA-capable marketplace app
may opt into a cross-region active-hot-standby Postgres pair for their
WordPress instance instead of the default single CNPG Cluster.

Mirrors the canonical bp-cnpg-pair pattern (primary + replica Cluster
CRs with WAL streaming over Cilium ClusterMesh via a managed Service
annotated service.cilium.io/global=true). When the new
pg.activeHotStandby.enabled flag is false (default), templates render
the existing single Cluster bit-for-bit — no regression for non-HA
tenants.

Catalog seed flags WordPress with ha + cnpg-pair tags so the marketplace
HA filter can surface it.

Chart bumped 0.2.1 -> 0.3.0. New render-gate test asserts both default
single-cluster shape AND the enabled 2-Cluster shape with the right
nodeSelectors, replica.source, externalCluster.host, Cilium global
annotation, and bootstrap.pg_basebackup; all 5 cases pass.

Co-authored-by: hatiyildiz <hatice.yildiz@openova.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 23:39:29 +04:00
github-actions[bot]
af4d9b1b87 deploy: update sme service images to f9ed292 + bump chart to 1.4.146 2026-05-16 19:29:50 +00:00
e3mrah
f9ed292198
fix(billing): /redeem-preview + plans + addons bypass JWT (D29) (#1561)
* chore(slot-13): pin bp-catalyst-platform to 1.4.145 (D29 gateway public routes)

PR #1559 added /api/billing/{vouchers/redeem-preview,plans,addons} as
public gateway routes — required for the marketplace /redeem zero-touch
flow. Pin the slot so future provisions inherit it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(billing): /redeem-preview + plans + addons bypass JWT (D29)

Mirror PR #1559's gateway public routes in the billing service's own
middleware chain. The gateway now lets these requests through without
an Authorization header (D29 voucher-redeem landing), but billing
service's main.go was JWT-gating EVERY /billing/* path except
/billing/webhook — so the request still got 401, just one hop later.

Caught live on t132 2026-05-16 after PR #1559 rolled.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: hatiyildiz <hatice.yildiz@openova.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 23:28:48 +04:00
e3mrah
936e76f79a
chore(slot-13): pin bp-catalyst-platform to 1.4.145 (D29 gateway public routes) (#1560)
PR #1559 added /api/billing/{vouchers/redeem-preview,plans,addons} as
public gateway routes — required for the marketplace /redeem zero-touch
flow. Pin the slot so future provisions inherit it.

Co-authored-by: hatiyildiz <hatice.yildiz@openova.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 23:25:14 +04:00
github-actions[bot]
696aa26f83 deploy: update sme service images to a11067d + bump chart to 1.4.145 2026-05-16 19:18:09 +00:00
e3mrah
a11067da1a
fix(gateway): /redeem-preview + plans + addons must be public (D29) (#1559)
* feat(billing+notification): wire voucher-issued email (D28)

D28 of the Sovereign DoD requires that issuing a voucher emails it to
the recipient zero-touch. Today POST /billing/vouchers/issue persists
the PromoCode row but never notifies anyone — so a gifted voucher only
reaches its recipient if the operator manually sends the code over a
side channel. This wires sme-billing -> sme-notification so the email
fires automatically on every successful upsert that carries a
recipient_email field.

Architecture follows the existing notification-service seam:
sme-billing POSTs to http://notification.sme.svc.cluster.local:8087/
notification/send with template=voucher-issued; sme-notification renders
the HTML and dispatches via Stalwart over SMTP. No direct SMTP code is
added to billing, no stalwart-mail calls bypass notification.

Server-side only — the owner-UI for issuing vouchers (D28b) is a
separate PR.

Changes:

  notification/templates/templates.go
    + VoucherIssuedEmail(code, creditOMR, description, sovereignFQDN,
      validityHint) — renders code prominently, redeem button to
      https://marketplace.<sovereignFQDN>/redeem/?code=<CODE>; FQDN
      always supplied by caller, NEVER hardcoded.

  notification/handlers/handlers.go
    + renderTemplate("voucher-issued") case parsing
      {code, credit_omr, description, sovereign_fqdn, validity_hint}.
    + Default subject "You've been gifted a voucher for OpenOva SME".

  billing/handlers/handlers.go
    + Handler fields: NotificationURL, SovereignFQDN, NotificationClient.

  billing/handlers/vouchers.go
    + issueVoucherRequest = store.PromoCode + RecipientEmail (request-
      only; never persisted).
    + sendVoucherIssuedEmail() — POSTs to NotificationURL with a 5s
      timeout. Best-effort: a non-2xx or transport error logs but does
      NOT fail the IssueVoucher response, because the row is already
      persisted and re-issuing the same code re-fires the email.
    + Re-issue semantics (#91 resurrects soft-deleted rows) extend to
      the email path — documented in the handler comment.

  billing/main.go
    + Reads NOTIFICATION_SERVICE_URL (default
      http://notification.sme.svc.cluster.local:8087/notification/send)
      and SOVEREIGN_FQDN env vars. Wires a 5s default http.Client.

  products/catalyst/chart/templates/sme-services/billing.yaml
    + Pipes NOTIFICATION_SERVICE_URL (cluster-DNS constant) and
      SOVEREIGN_FQDN (from .Values.global.sovereignFQDN, NEVER
      hardcoded) into the billing Deployment.

Tests:

  notification/handlers/handlers_test.go (new)
    + TestRenderTemplate_VoucherIssued: rendered HTML contains code +
      credit + a redeem URL built from the supplied FQDN; never falls
      back to marketplace.openova.io.
    + TestRenderTemplate_VoucherIssued_CustomSubject + _NoDescription
      + TestRenderTemplate_UnknownTemplate as guard rails.

  billing/handlers/vouchers_test.go
    + TestIssueVoucher_SendsEmail_WhenRecipientPresent: a fake round-
      tripper sees the POST to notification with the right URL +
      template + data (code upper-cased, credit_omr, sovereign_fqdn,
      description) when recipient_email is set.
    + TestIssueVoucher_NoEmail_WhenRecipientAbsent: no notification
      call when recipient is empty.
    + TestIssueVoucher_NotificationFailure_DoesNotFailUpsert:
      operator gets 200 even when notification returns 500.
    + TestIssueVoucher_403WithoutVoucherIssuerRole: role gate preserved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(chart): admin pod uses dedicated image tag (D27 SME stack)

t132 caught admin pod stuck in ImagePullBackOff on `admin:b0ed216` —
the SME services CI run for that mono-repo SHA published 10 services
but admin's image was missing from GHCR. Decouple admin's tag from
smeTag so a missing-build for one service doesn't wedge the SME stack.

Default to `3c2f7e4` (matches marketplaceApi + console, known-published).
When admin's UI changes, bump in lockstep with those.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(slot-13): pin bp-catalyst-platform to 1.4.144

PR #1556 (D28 voucher email wire) + PR #1557 (D27 admin tag override)
landed and Blueprint Release packaged 1.4.144. Pin the slot file so
future provisions get the latest chart by default — t132 manually
upgraded via kubectl patch but t133+ will inherit it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(gateway): /redeem-preview + plans + addons must be public (D29)

The marketplace /redeem?code=XXX landing page calls
/api/billing/vouchers/redeem-preview unauthenticated per docs/FRANCHISE-
MODEL.md §3, but the gateway's catch-all /api/billing/ entry was
returning 401 to it — breaking the entire voucher-redeem zero-touch
flow that D29 depends on.

Also expose /api/billing/plans and /api/billing/addons so the
marketplace landing can render pricing without a session.

Caught live on t132 2026-05-16 — every /redeem call returned 401.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: hatiyildiz <hatice.yildiz@openova.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 23:17:04 +04:00
e3mrah
27bd5d486d
chore(slot-13): pin bp-catalyst-platform to 1.4.144 (#1558)
* feat(billing+notification): wire voucher-issued email (D28)

D28 of the Sovereign DoD requires that issuing a voucher emails it to
the recipient zero-touch. Today POST /billing/vouchers/issue persists
the PromoCode row but never notifies anyone — so a gifted voucher only
reaches its recipient if the operator manually sends the code over a
side channel. This wires sme-billing -> sme-notification so the email
fires automatically on every successful upsert that carries a
recipient_email field.

Architecture follows the existing notification-service seam:
sme-billing POSTs to http://notification.sme.svc.cluster.local:8087/
notification/send with template=voucher-issued; sme-notification renders
the HTML and dispatches via Stalwart over SMTP. No direct SMTP code is
added to billing, no stalwart-mail calls bypass notification.

Server-side only — the owner-UI for issuing vouchers (D28b) is a
separate PR.

Changes:

  notification/templates/templates.go
    + VoucherIssuedEmail(code, creditOMR, description, sovereignFQDN,
      validityHint) — renders code prominently, redeem button to
      https://marketplace.<sovereignFQDN>/redeem/?code=<CODE>; FQDN
      always supplied by caller, NEVER hardcoded.

  notification/handlers/handlers.go
    + renderTemplate("voucher-issued") case parsing
      {code, credit_omr, description, sovereign_fqdn, validity_hint}.
    + Default subject "You've been gifted a voucher for OpenOva SME".

  billing/handlers/handlers.go
    + Handler fields: NotificationURL, SovereignFQDN, NotificationClient.

  billing/handlers/vouchers.go
    + issueVoucherRequest = store.PromoCode + RecipientEmail (request-
      only; never persisted).
    + sendVoucherIssuedEmail() — POSTs to NotificationURL with a 5s
      timeout. Best-effort: a non-2xx or transport error logs but does
      NOT fail the IssueVoucher response, because the row is already
      persisted and re-issuing the same code re-fires the email.
    + Re-issue semantics (#91 resurrects soft-deleted rows) extend to
      the email path — documented in the handler comment.

  billing/main.go
    + Reads NOTIFICATION_SERVICE_URL (default
      http://notification.sme.svc.cluster.local:8087/notification/send)
      and SOVEREIGN_FQDN env vars. Wires a 5s default http.Client.

  products/catalyst/chart/templates/sme-services/billing.yaml
    + Pipes NOTIFICATION_SERVICE_URL (cluster-DNS constant) and
      SOVEREIGN_FQDN (from .Values.global.sovereignFQDN, NEVER
      hardcoded) into the billing Deployment.

Tests:

  notification/handlers/handlers_test.go (new)
    + TestRenderTemplate_VoucherIssued: rendered HTML contains code +
      credit + a redeem URL built from the supplied FQDN; never falls
      back to marketplace.openova.io.
    + TestRenderTemplate_VoucherIssued_CustomSubject + _NoDescription
      + TestRenderTemplate_UnknownTemplate as guard rails.

  billing/handlers/vouchers_test.go
    + TestIssueVoucher_SendsEmail_WhenRecipientPresent: a fake round-
      tripper sees the POST to notification with the right URL +
      template + data (code upper-cased, credit_omr, sovereign_fqdn,
      description) when recipient_email is set.
    + TestIssueVoucher_NoEmail_WhenRecipientAbsent: no notification
      call when recipient is empty.
    + TestIssueVoucher_NotificationFailure_DoesNotFailUpsert:
      operator gets 200 even when notification returns 500.
    + TestIssueVoucher_403WithoutVoucherIssuerRole: role gate preserved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(chart): admin pod uses dedicated image tag (D27 SME stack)

t132 caught admin pod stuck in ImagePullBackOff on `admin:b0ed216` —
the SME services CI run for that mono-repo SHA published 10 services
but admin's image was missing from GHCR. Decouple admin's tag from
smeTag so a missing-build for one service doesn't wedge the SME stack.

Default to `3c2f7e4` (matches marketplaceApi + console, known-published).
When admin's UI changes, bump in lockstep with those.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(slot-13): pin bp-catalyst-platform to 1.4.144

PR #1556 (D28 voucher email wire) + PR #1557 (D27 admin tag override)
landed and Blueprint Release packaged 1.4.144. Pin the slot file so
future provisions get the latest chart by default — t132 manually
upgraded via kubectl patch but t133+ will inherit it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: hatiyildiz <hatice.yildiz@openova.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 23:12:34 +04:00
github-actions[bot]
48eb653f79 deploy: update sme service images to 1fe7067 + bump chart to 1.4.144 2026-05-16 19:05:51 +00:00
e3mrah
7c3724591c
fix(chart): admin pod uses dedicated image tag (D27 SME stack) (#1557)
* feat(billing+notification): wire voucher-issued email (D28)

D28 of the Sovereign DoD requires that issuing a voucher emails it to
the recipient zero-touch. Today POST /billing/vouchers/issue persists
the PromoCode row but never notifies anyone — so a gifted voucher only
reaches its recipient if the operator manually sends the code over a
side channel. This wires sme-billing -> sme-notification so the email
fires automatically on every successful upsert that carries a
recipient_email field.

Architecture follows the existing notification-service seam:
sme-billing POSTs to http://notification.sme.svc.cluster.local:8087/
notification/send with template=voucher-issued; sme-notification renders
the HTML and dispatches via Stalwart over SMTP. No direct SMTP code is
added to billing, no stalwart-mail calls bypass notification.

Server-side only — the owner-UI for issuing vouchers (D28b) is a
separate PR.

Changes:

  notification/templates/templates.go
    + VoucherIssuedEmail(code, creditOMR, description, sovereignFQDN,
      validityHint) — renders code prominently, redeem button to
      https://marketplace.<sovereignFQDN>/redeem/?code=<CODE>; FQDN
      always supplied by caller, NEVER hardcoded.

  notification/handlers/handlers.go
    + renderTemplate("voucher-issued") case parsing
      {code, credit_omr, description, sovereign_fqdn, validity_hint}.
    + Default subject "You've been gifted a voucher for OpenOva SME".

  billing/handlers/handlers.go
    + Handler fields: NotificationURL, SovereignFQDN, NotificationClient.

  billing/handlers/vouchers.go
    + issueVoucherRequest = store.PromoCode + RecipientEmail (request-
      only; never persisted).
    + sendVoucherIssuedEmail() — POSTs to NotificationURL with a 5s
      timeout. Best-effort: a non-2xx or transport error logs but does
      NOT fail the IssueVoucher response, because the row is already
      persisted and re-issuing the same code re-fires the email.
    + Re-issue semantics (#91 resurrects soft-deleted rows) extend to
      the email path — documented in the handler comment.

  billing/main.go
    + Reads NOTIFICATION_SERVICE_URL (default
      http://notification.sme.svc.cluster.local:8087/notification/send)
      and SOVEREIGN_FQDN env vars. Wires a 5s default http.Client.

  products/catalyst/chart/templates/sme-services/billing.yaml
    + Pipes NOTIFICATION_SERVICE_URL (cluster-DNS constant) and
      SOVEREIGN_FQDN (from .Values.global.sovereignFQDN, NEVER
      hardcoded) into the billing Deployment.

Tests:

  notification/handlers/handlers_test.go (new)
    + TestRenderTemplate_VoucherIssued: rendered HTML contains code +
      credit + a redeem URL built from the supplied FQDN; never falls
      back to marketplace.openova.io.
    + TestRenderTemplate_VoucherIssued_CustomSubject + _NoDescription
      + TestRenderTemplate_UnknownTemplate as guard rails.

  billing/handlers/vouchers_test.go
    + TestIssueVoucher_SendsEmail_WhenRecipientPresent: a fake round-
      tripper sees the POST to notification with the right URL +
      template + data (code upper-cased, credit_omr, sovereign_fqdn,
      description) when recipient_email is set.
    + TestIssueVoucher_NoEmail_WhenRecipientAbsent: no notification
      call when recipient is empty.
    + TestIssueVoucher_NotificationFailure_DoesNotFailUpsert:
      operator gets 200 even when notification returns 500.
    + TestIssueVoucher_403WithoutVoucherIssuerRole: role gate preserved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(chart): admin pod uses dedicated image tag (D27 SME stack)

t132 caught admin pod stuck in ImagePullBackOff on `admin:b0ed216` —
the SME services CI run for that mono-repo SHA published 10 services
but admin's image was missing from GHCR. Decouple admin's tag from
smeTag so a missing-build for one service doesn't wedge the SME stack.

Default to `3c2f7e4` (matches marketplaceApi + console, known-published).
When admin's UI changes, bump in lockstep with those.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: hatiyildiz <hatice.yildiz@openova.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 23:05:09 +04:00
e3mrah
1fe706769f
feat(billing+notification): wire voucher-issued email (D28) (#1556)
D28 of the Sovereign DoD requires that issuing a voucher emails it to
the recipient zero-touch. Today POST /billing/vouchers/issue persists
the PromoCode row but never notifies anyone — so a gifted voucher only
reaches its recipient if the operator manually sends the code over a
side channel. This wires sme-billing -> sme-notification so the email
fires automatically on every successful upsert that carries a
recipient_email field.

Architecture follows the existing notification-service seam:
sme-billing POSTs to http://notification.sme.svc.cluster.local:8087/
notification/send with template=voucher-issued; sme-notification renders
the HTML and dispatches via Stalwart over SMTP. No direct SMTP code is
added to billing, no stalwart-mail calls bypass notification.

Server-side only — the owner-UI for issuing vouchers (D28b) is a
separate PR.

Changes:

  notification/templates/templates.go
    + VoucherIssuedEmail(code, creditOMR, description, sovereignFQDN,
      validityHint) — renders code prominently, redeem button to
      https://marketplace.<sovereignFQDN>/redeem/?code=<CODE>; FQDN
      always supplied by caller, NEVER hardcoded.

  notification/handlers/handlers.go
    + renderTemplate("voucher-issued") case parsing
      {code, credit_omr, description, sovereign_fqdn, validity_hint}.
    + Default subject "You've been gifted a voucher for OpenOva SME".

  billing/handlers/handlers.go
    + Handler fields: NotificationURL, SovereignFQDN, NotificationClient.

  billing/handlers/vouchers.go
    + issueVoucherRequest = store.PromoCode + RecipientEmail (request-
      only; never persisted).
    + sendVoucherIssuedEmail() — POSTs to NotificationURL with a 5s
      timeout. Best-effort: a non-2xx or transport error logs but does
      NOT fail the IssueVoucher response, because the row is already
      persisted and re-issuing the same code re-fires the email.
    + Re-issue semantics (#91 resurrects soft-deleted rows) extend to
      the email path — documented in the handler comment.

  billing/main.go
    + Reads NOTIFICATION_SERVICE_URL (default
      http://notification.sme.svc.cluster.local:8087/notification/send)
      and SOVEREIGN_FQDN env vars. Wires a 5s default http.Client.

  products/catalyst/chart/templates/sme-services/billing.yaml
    + Pipes NOTIFICATION_SERVICE_URL (cluster-DNS constant) and
      SOVEREIGN_FQDN (from .Values.global.sovereignFQDN, NEVER
      hardcoded) into the billing Deployment.

Tests:

  notification/handlers/handlers_test.go (new)
    + TestRenderTemplate_VoucherIssued: rendered HTML contains code +
      credit + a redeem URL built from the supplied FQDN; never falls
      back to marketplace.openova.io.
    + TestRenderTemplate_VoucherIssued_CustomSubject + _NoDescription
      + TestRenderTemplate_UnknownTemplate as guard rails.

  billing/handlers/vouchers_test.go
    + TestIssueVoucher_SendsEmail_WhenRecipientPresent: a fake round-
      tripper sees the POST to notification with the right URL +
      template + data (code upper-cased, credit_omr, sovereign_fqdn,
      description) when recipient_email is set.
    + TestIssueVoucher_NoEmail_WhenRecipientAbsent: no notification
      call when recipient is empty.
    + TestIssueVoucher_NotificationFailure_DoesNotFailUpsert:
      operator gets 200 even when notification returns 500.
    + TestIssueVoucher_403WithoutVoucherIssuerRole: role gate preserved.

Co-authored-by: hatiyildiz <hatice.yildiz@openova.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 23:04:46 +04:00
github-actions[bot]
9718ba2924 deploy: update catalyst images to 2fd4e3c 2026-05-16 18:26:16 +00:00
e3mrah
2fd4e3cbf4
feat(wizard): default marketplaceEnabled=true for D27 zero-touch (#1555)
Founder ruling 2026-05-16: D27 mandates that a fresh wizard provisions a
Sovereign already ready to host tenant orgs (D29). Operator can still
flip the toggle off on StepMarketplace if they explicitly want a
private Sovereign.

Co-authored-by: hatiyildiz <hatice.yildiz@openova.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 22:24:09 +04:00
e3mrah
77c80c9728
docs(DoD): add D27-D31 (marketplace + voucher + tenant org + free subdomain + CNPG active-hot-standby) (#1554)
Founder ruling 2026-05-16: tenant onboarding flow is part of the Sovereign DoD.

D27 — Marketplace enabled on the Sovereign (zero-touch from provision body)
D28 — Owner-tier voucher issuance (one-click, voucher mailed via Sovereign SMTP)
D29 — Voucher-redeem → org wizard → tenant namespace+RBAC+bootstrap (zero-touch)
D30 — Free-subdomain pool selection (omani.homes, omani.rest, omani.trades)
D31 — Tenant app with CNPG active-hot-standby cross-region replication

Co-authored-by: hatiyildiz <hatice.yildiz@openova.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 22:18:55 +04:00
github-actions[bot]
564fe4f4e5 deploy: update catalyst images to 9f096b0 2026-05-16 18:01:02 +00:00
e3mrah
9f096b0b18
fix(chroot): populate Result.LoadBalancerIP so canvas shows LB chip (D15) (#1553)
chrootEnsureDeployment was synthesizing a Deployment with Result=nil.
The topology loader's buildLBs() returned [] on nil-Result → canvas
chip showed `LoadBalancer 0/0` on every chroot Sovereign Console
even though the Sovereign ingress LB was allocated and serving
console.<fqdn>.

Populate Result with LoadBalancerIP from `SOVEREIGN_LB_IP` env (set
by bp-catalyst-platform's sovereign-fqdn ConfigMap `lbIP` key per
issue #900 / PR #145). buildLBs then emits one LoadBalancer entry
per region using the canonical primary LB.

Caught on t131 2026-05-16 — DoD D15. Same chroot-synth-enrichment
pattern as PR #1534 (SOVEREIGN_REGIONS_JSON).

Co-authored-by: hatiyildiz <hatice.yildiz@openova.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 21:58:53 +04:00
github-actions[bot]
dd9b631740 deploy: update catalyst images to 124ac13 2026-05-16 17:58:31 +00:00
e3mrah
124ac13c1d
fix(router): chroot Sovereign /app/<name> resolves to AppDetail, not mothership AppsPage (D17b) (#1552)
Two route trees claim `/app`:

1. `appRoute` (line 364) — mothership AppLayout chrome, prefix `/app`,
   children `/app/$deploymentId/applications/*`, `/app/$deploymentId/
   settings`, `/app/dashboard` (fleet view), etc. ~30 children.
2. `consoleAppDetailRoute` (line 1141, under consoleLayoutRoute) —
   clean `/app/$componentId` for the chroot Sovereign Console's
   per-app detail.

On a chroot Sovereign Console (DETECTED_MODE.mode === 'sovereign')
the operator clicks `/apps/<card>` → AppCard generates HREF
`/app/<name>` (AppsPage.tsx line ~720, correct for chroot context).
TanStack router resolves to the MOTHERSHIP `appRoute` because it
matches first (registered earlier under rootRoute) and its
children accept `<name>` as $deploymentId. The page renders
AppLayout chrome + AppsPage with mothership sidebar — looks
nothing like AppDetail.

Founder observation (BUG-002 from /tmp/test-matrix-t129.json + reported
on t131 2026-05-16):
> Application individual pages are not visible at all in the child
> while mothership doesn't have that issue, this is the biggest blunder!

Fix: `appRoute.beforeLoad` redirects on chroot:
- `/app/<componentId>` → `/<componentId>` (caught by consoleAppDetailRoute)
- `/app/dashboard`, `/app/install`, `/app/sre/*`, `/app/sec/*`, `/app/blueprints`
  → `/dashboard` (canonical Sovereign landing; these are mothership-only
  surfaces — already partially fixed at dashboardRoute level by PR #1547)

Mothership behavior unchanged (DETECTED_MODE.mode !== 'sovereign'
falls through to the existing AppLayout-rooted tree).

Refs DoD D17b. Caught on t131 (623354058b114dd6, 2026-05-16).

Co-authored-by: hatiyildiz <hatice.yildiz@openova.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 21:56:31 +04:00
e3mrah
f88e60726c
fix(slot-13): single-quote SOVEREIGN_REGIONS_JSON to preserve JSON literal (D5) (#1551)
The substitute `${SOVEREIGN_REGIONS_JSON:-}` produces valid JSON like
`[{"cloudRegion":"hel1","controlPlaneSize":"cpx52",...}]`. Unquoted in
the slot-13 YAML, the YAML parser interprets it as a flow-sequence
of flow-mappings, parsing into Go `[]map[string]interface{}`. Helm
chart template `{{ .Values.sovereign.regionsJson }}` then stringifies
via `%v` printf, producing Go map syntax:

  [map[cloudRegion:hel1 controlPlaneSize:cpx52 ...]]

The chroot catalyst-api's `chrootRegionsFromEnv` calls
json.Unmarshal which fails → Request.Regions stays empty → topology
loader falls back to live-Nodes path → /cloud renders "1 region 1
cluster" on every multi-region Sovereign.

Caught on t131 (623354058b114dd6, 2026-05-16) — DoD D5.

Fix: single-quote the substitute so YAML treats it as a string literal,
preserving the JSON byte-for-byte.

Co-authored-by: hatiyildiz <hatice.yildiz@openova.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 21:33:23 +04:00
e3mrah
7e87a4d7b9
fix(clustermesh-lb): revert use-private-ip to false (D11) (#1550)
PR #1537 set `use-private-ip: "true"` on the clustermesh-apiserver
Service annotations. CCM rejected with:

  ReconcileHCLBTargets: use private ip: missing network id

The per-region Hetzner LB allocated by CCM has no private-network
attachment by default (LB private_net is empty), so it can't route
to the backend's private IP. Result: LB never allocated, clustermesh
apiserver Service stays `<pending>`, orchestrator waits 5min and
bails with empty peerEntries. Caught on t130 (30463cd0a5a931be,
2026-05-16).

PR #1538's canonical fix opens TCP 30000-32767 in the Hetzner
firewall so the public-IP LB→backend health checks pass. Revert
use-private-ip to false so the chain works end-to-end.

Refs DoD D11.

Co-authored-by: hatiyildiz <hatice.yildiz@openova.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 21:01:20 +04:00
github-actions[bot]
8980b727fb deploy: update catalyst images to fbe23da 2026-05-16 16:34:04 +00:00
e3mrah
fbe23da091
fix(ui-nginx): allow Google Fonts domains in CSP (D26) (#1549)
Sovereign Console pages reference Inter + JetBrains Mono fonts via
fonts.googleapis.com (index.html lines 9, 11). The nginx CSP only
allowed font-src 'self' data: — so the browser blocked the font
stylesheet AND the woff2 fetches, falling back to system fonts.

Add fonts.googleapis.com to style-src (for the @import CSS) and
fonts.gstatic.com to font-src (for the woff2 assets). All 3 CSP
occurrences in nginx.conf updated identically.

Alternative considered: self-host the woff2 + drop the external
references. Skipped for now — sticking with Google Fonts CDN is
faster + matches every other web app's posture. If the operator
wants air-gap-compatible Sovereigns later, switch to self-hosted.

Caught on t129 2026-05-16 — DoD D26.

Co-authored-by: hatiyildiz <hatice.yildiz@openova.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 20:31:51 +04:00
github-actions[bot]
27556577f7 deploy: update catalyst images to 7845a00 2026-05-16 16:30:19 +00:00
e3mrah
7845a00799
fix(dashboard): add region + vcluster as TreemapDimensions (D16) (#1548)
Multi-region operators on the Sovereign Console couldn't pivot the
/dashboard treemap by region or vCluster. The TreemapDimension
union (FE) and dashboardDimension set (BE) only included
sovereign/cluster/family/namespace/application.

This PR:
- Adds 'region' + 'vcluster' to TreemapDimension type
  (products/catalyst/bootstrap/ui/src/lib/treemap.types.ts)
- Adds them to the dimension select options
  (products/catalyst/bootstrap/ui/src/components/TreemapLayerController.tsx)
- Adds them to the validated set in dashboard.go
- Adds podRow.region + podRow.vcluster fields populated from
  openova.io/region and catalyst.openova.io/vcluster-role labels
- Extends dimensionKey switch to bucket by these new dimensions
  (fallback: region→cluster, vcluster→"host")

Caught on t129 2026-05-16 — DoD D16. Note that full multi-cluster
fan-out (aggregating pods across all 3 region kubeconfigs into one
treemap) is a separate refactor not included here; this PR delivers
the dimension surface so the layer selector is usable + a fresh prov
with the chroot's k8scache extended to multi-region will render
3 cluster bubbles when the operator picks Layer-1=cluster.

Co-authored-by: hatiyildiz <hatice.yildiz@openova.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 20:24:34 +04:00
github-actions[bot]
477bd0966f deploy: update catalyst images to 52015ff 2026-05-16 16:15:32 +00:00
e3mrah
52015ff468
fix(ui): t129 SPA routing — bp-bp- prefix, PIN /wizard leak, /app/dashboard fleet leak (#1547)
Three operator-visible SPA routing bugs caught on live t129 Sovereign
Console (t129.omani.works, 2026-05-16). Closes #1546.

BUG-001 (D19) — doubled /app/bp-bp-* href on 10 of 44 app cards.
  build-catalog.mjs::listBootstrapKit extracted slug from `NN-(.+)\.yaml`
  without stripping an optional `bp-` already present in some filenames
  (e.g. `13-bp-catalyst-platform.yaml`). The captured slug became
  `bp-catalyst-platform`, then `id: \`bp-${slug}\`` doubled it to
  `bp-bp-catalyst-platform`, breaking the FE↔BE HR-name join and
  printing the doubled prefix on the AppsPage card href. Fix: strip a
  leading `bp-` from the captured slug before forming the canonical id.
  Regenerated catalog.generated.ts + blueprints.json — 10 entries
  collapse to their single-prefix canonical form (bp-catalyst-platform,
  bp-cert-manager-powerdns-webhook, bp-k8s-ws-proxy, bp-guacamole,
  bp-dmz-vcluster, bp-hcloud-ccm, bp-openova-flow-server,
  bp-openova-flow-emitter, bp-mgmt-vcluster, bp-rtz-vcluster).

BUG-015 (D23, extends D0) — PIN-verify lands /wizard on Sovereign.
  VerifyPinPage default landing was `/wizard` regardless of operating
  mode. On a chroot Sovereign Console (DETECTED_MODE.mode === 'sovereign'
  the operator has just been auto-redirected from the mothership
  handover URL; their Sovereign is already converged. Routing them to
  the new-prov wizard re-prompts for org details and contradicts D0.
  Fix: branch on DETECTED_MODE.mode — `/dashboard` on sovereign,
  `/wizard` on catalyst-zero. Mothership flow unchanged. Test:
  VerifyPinPage.test.tsx asserts the 3 cases (sovereign default,
  catalyst-zero default, explicit next= override).

BUG-016 (D24) — /app/dashboard exposes mothership fleet view.
  appRoute's `/dashboard` child mounts DashboardPage (multi-Sovereign
  fleet, "7 Sovereigns" with duplicate rows). On a Sovereign Console
  this surface MUST NOT be reachable — the Sovereign owns ONE deployment,
  fleet is mothership-only. Fix: beforeLoad on dashboardRoute redirects
  to `/dashboard` (consoleDashboardRoute, the per-Sovereign landing)
  when DETECTED_MODE.mode === 'sovereign'. Mothership keeps the fleet
  view as today.

Refs: docs/SOVEREIGN-MULTI-REGION-DOD.md D19/D23/D24,
      /tmp/test-matrix-t129.json discoveries BUG-001/015/016.

Co-authored-by: hatiyildiz <hatice.yildiz@openova.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 20:13:26 +04:00
e3mrah
d7b2c017f1
fix(gitea): override DOMAIN/ROOT_URL with SOVEREIGN_FQDN (D25) (#1545)
Chart values.yaml ships `gitea.gitea.config.server.DOMAIN = gitea.catalyst.local`
+ `ROOT_URL = https://gitea.catalyst.local` — the bootstrap dev hostname.
Without per-Sovereign override, Gitea's Web UI rendered the dev
hostname in pageData.appUrl, internal links, and `git clone` URLs.
Operators on every freshly-provisioned Sovereign were shown a
gitea.catalyst.local hostname that public DNS can't resolve.

Slot 10-gitea Kustomization adds the per-Sovereign override:
  gitea.gitea.config.server.DOMAIN: gitea.${SOVEREIGN_FQDN}
  gitea.gitea.config.server.ROOT_URL: https://gitea.${SOVEREIGN_FQDN}

Caught on t129 2026-05-16 — DoD D25.

Co-authored-by: hatiyildiz <hatice.yildiz@openova.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 19:57:43 +04:00