# bp-crossplane-claims has no operator-tunable values for the
# core compose.openova.io/v1alpha1 family — those XRDs and Compositions
# are static manifests with no Go-template substitutions. This file
# exists so `helm template` and the CI smoke render have an explicit
# defaults document; future per-Sovereign overrides (e.g.
# defaultCompositionRef swaps for a non-Hetzner cloud) would land here.

global:
  # When set, ALL Docker image pulls in this chart route through this registry.
  # Used post-handover when the Sovereign's own Harbor takes over the proxy_cache
  # role from contabo's central Harbor. Empty = no rewrite. Tracked under #560.
  #
  # NOTE: Hetzner cloud server images (e.g. ubuntu-24.04 in Crossplane Server
  # resources) are NOT Docker images and MUST NOT be registry-prefixed.
  # Only the kubectl Job container image is affected by this knob.
  imageRegistry: ""

  # kubectl container image used in XNodeActionClaim Jobs.
  # Repository and tag are separately configurable so post-handover per-Sovereign
  # overlays can point at a Harbor-cached copy without rebuilding this Blueprint.
  #
  # 2026-05-11 (Fix #158): switched from docker.io/bitnami/kubectl:1.31
  # because Bitnami's 2025-08 secure-images cutover deleted all
  # versioned tags from docker.io/bitnami/kubectl (only :latest +
  # sha256-named tags remain). bitnamilegacy is Bitnami's deprecation-
  # fallback registry path; retains bash/sh.
  #
  # 2026-05-11 (Fix #163, MIRROR-EVERYTHING): explicit
  # harbor.openova.io/proxy-dockerhub prefix per CLAUDE.md inviolable
  # rule. Operator override still works — entire `repository` is one
  # configurable string.
  kubectlImage:
    repository: harbor.openova.io/proxy-dockerhub/bitnamilegacy/kubectl
    tag: "1.31.4"

catalystBlueprint:
  upstream: null

# Sovereign IAM access plane (epic #320). Renders the
# access.openova.io/v1alpha1 XUserAccess XRD and the three canonical
# openova:application-{admin,editor,viewer} ClusterRoles that the
# legacy Composition's RoleBindings reference. Default-on; toggle-off
# is reserved for the rare case where a Sovereign uses an external IAM
# stack and does not consume the Catalyst access plane.
#
# IMPORTANT: the XRD MUST stay enabled because it owns the
# `useraccesses.access.openova.io` CRD that the catalyst-useraccess-controller
# (products/catalyst/chart/templates/controllers/useraccess-controller-*)
# watches as its primary resource. Removing the XRD would un-register
# the CRD and silence the controller.
#
# The legacy Composition path (`compositionEnabled`) is now retired by
# default — see the long-form note on `compositionEnabled` below.
userAccess:
  enabled: true

  # Legacy Crossplane Composition path for XUserAccess (qa-loop iter-16
  # Fix #71 — superseded by catalyst-useraccess-controller).
  #
  # ROOT CAUSE FROM LIVE: with `compositionEnabled: true` the Crossplane
  # composite controller fights the catalyst-useraccess-controller over
  # status on every UserAccess CR — both write `.status.conditions[]`
  # (Ready/Synced) and the composite controller's last-write wins.
  # Composite controller writes `Ready=False` because:
  #   1) provider-kubernetes is NOT installed on production Sovereigns
  #      (only provider-hcloud ships in clusters/<sovereign>/infrastructure/),
  #      so the Composition's Object resource has no ProviderConfig kind
  #      to land against;
  #   2) post-EPIC-3 (#1098) UserAccess CRs are authored with
  #      `tierRoleRef`+`scopes[]` and NO `applications[]` — the legacy
  #      Composition only patches `spec.applications[0]` and emits an
  #      Object with empty roleRef/namespace/subjects.
  #
  # The controller (catalyst-useraccess-controller) is the canonical
  # day-2 path per the deployment-template header
  # (products/catalyst/chart/templates/controllers/useraccess-controller-deployment.yaml
  # lines 11-16): "Operators MUST disable the Crossplane path (delete the
  # UserAccess Composition) BEFORE flipping `useraccess.enabled=true`".
  # That cutover is now baked into chart defaults: the controller is
  # `enabled: true` in products/catalyst/chart/values.yaml, and this
  # gate ships `false` so the legacy Composition is never installed.
  #
  # Setting this to `true` is reserved for a Sovereign that genuinely
  # cannot run the controller (e.g. air-gapped image-pull lockdown
  # without the GHCR controller image cached) — and even then the
  # operator MUST also install provider-kubernetes via
  # clusters/<sovereign>/infrastructure/provider-kubernetes.yaml +
  # ProviderConfig before flipping this on.
  compositionEnabled: false

# 5-tier catalog system (EPIC-3 #1098, slice T1). Renders 5 ClusterRoles
# `openova:tier-{viewer,developer,operator,admin,owner}` that the
# `/rbac/assign` find-or-create endpoint (slice A1) targets via roleRef.
#
# The action sets (RBAC verbs) are operator-tunable per
# docs/INVIOLABLE-PRINCIPLES.md #4. The Helm template at
# templates/tier-clusterroles.yaml computes the inherit chain
# (developer ⊇ viewer; operator ⊇ developer; admin ⊇ operator;
# owner ⊇ admin) by walking `inherit` recursively and concatenating the
# resulting `extras[]` rule slices. enforcedScopes[] surfaces in the
# rendered ClusterRole's annotations and is the contract the
# useraccess-controller (slice T3) reads when deciding whether to
# auto-inject `openova.io/env-type=dev` on developer-tier UserAccess CRs.
#
# Why ClusterRoles rather than Roles: per ADR-0001 §2.7 tenancy is
# K8s-native. UserAccess can target either a single namespace
# (RoleBinding → tier ClusterRole) or cluster-wide (ClusterRoleBinding
# → tier ClusterRole). Same ClusterRole works for both bindings.
#
# Why the action verbs are spelled out (not `*`): per `clusterroles.yaml`
# header — the audit trail must be grep-able. `*` is allowed here
# (lower-blast-radius cases) but kept rare; explicit verb lists are the
# default.
tiers:
  enabled: true
  # Tier ordering — the access-matrix UI reads the level integer from the
  # rendered ClusterRole's `catalyst.openova.io/tier-level` label so it
  # can sort tiers without a hardcoded list. Same integers the Keycloak
  # realm-role attribute `tier-level` carries (admin_roles.go:88-92).
  levels:
    viewer: 10
    developer: 20
    operator: 30
    admin: 40
    owner: 50

# Per-tier action set. `inherit` references another tier's full action
# set; `extras[]` is the verb list this tier ADDS on top. Empty `extras`
# is allowed (a tier that re-exposes its parent's set with no additions).
# `enforcedScopes[]` is auto-injected onto matching UserAccess CRs by
# the useraccess-controller — see core/controllers/internal/labels/
# `EnforcedScopes()`. The map is the public contract; renaming a tier
# breaks the controller's switch statement, so DO NOT rename — extend.
tierActions:
  # ── tier-viewer (level 10): *.read ───────────────────────────────────
  viewer:
    extras:
      - apiGroups: [""]
        resources:
          - pods
          - pods/log
          - services
          - configmaps
          - persistentvolumeclaims
          - serviceaccounts
          - events
          - namespaces
        verbs: [get, list, watch]
      - apiGroups: ["apps"]
        resources: [deployments, statefulsets, daemonsets, replicasets]
        verbs: [get, list, watch]
      - apiGroups: ["batch"]
        resources: [jobs, cronjobs]
        verbs: [get, list, watch]
      - apiGroups: ["networking.k8s.io"]
        resources: [ingresses, networkpolicies]
        verbs: [get, list, watch]
      - apiGroups: ["apps.openova.io"]
        resources: [applications]
        verbs: [get, list, watch]
      - apiGroups: ["catalyst.openova.io"]
        resources: [environments, blueprints]
        verbs: [get, list, watch]

  # ── tier-developer (level 20): viewer + exec/console/tickets/sessions
  # Auto-injected scope: openova.io/env-type=dev. Developers can NEVER
  # operate on prod (slice T3 enforces).
  developer:
    inherit: viewer
    extras:
      # workloads.exec — pod exec + portforward write-equivalents.
      - apiGroups: [""]
        resources: [pods/exec, pods/portforward]
        verbs: [create, get]
      # workloads.console — terminal session create (catalyst.openova.io
      # custom resource representing an open Guacamole console session).
      - apiGroups: ["catalyst.openova.io"]
        resources: [consolesessions]
        verbs: [create, get, list, watch, delete]
      # tickets.create + tickets.update.
      - apiGroups: ["catalyst.openova.io"]
        resources: [tickets]
        verbs: [create, get, list, watch, update, patch]
      # sessions.playback — read recorded sessions for review.
      - apiGroups: ["catalyst.openova.io"]
        resources: [sessions, sessions/playback]
        verbs: [get, list, watch]
    enforcedScopes:
      - key: openova.io/env-type
        value: dev

  # ── tier-operator (level 30): developer + connect.admin/sam/patches
  operator:
    inherit: developer
    extras:
      # console.connect.admin — open admin-level Guacamole consoles.
      - apiGroups: ["catalyst.openova.io"]
        resources: [consolesessions/admin]
        verbs: [create, get]
      # sam.manage — service-account-management; rotate SA keys etc.
      - apiGroups: [""]
        resources: [serviceaccounts/token]
        verbs: [create, get]
      - apiGroups: [""]
        resources: [serviceaccounts]
        verbs: [create, update, patch, delete]
      # patches.manage — apply patches to Applications.
      - apiGroups: ["apps.openova.io"]
        resources: [applications/patch]
        verbs: [create, get, update, patch]
      # tickets.accept — operators are the assignee class.
      - apiGroups: ["catalyst.openova.io"]
        resources: [tickets/accept]
        verbs: [create, get, update, patch]
      # continuum.* — DR posture: operators read continuum/cnpgpair/pdm,
      # initiate switchover/failback (the request endpoints below patch
      # spec.switchover.requested; the catalyst-api server-side gate
      # `applicationInstallCallerAuthorized` enforces operator-or-higher
      # in addition to this kubeapi RBAC). Per EPIC-6 iter-6 #1101.
      - apiGroups: ["dr.openova.io"]
        resources: [continuums, cnpgpairs, pdms]
        verbs: [get, list, watch, update, patch]
      - apiGroups: ["dr.openova.io"]
        resources: [continuums/status, cnpgpairs/status, pdms/status]
        verbs: [get, list, watch]
      # cnpg.* — read-only on the underlying CNPG cluster + backup CRs
      # operators inspect on switchover triage.
      - apiGroups: ["postgresql.cnpg.io"]
        resources: [clusters, clusters/status, backups, backups/status, scheduledbackups]
        verbs: [get, list, watch]

  # ── tier-admin (level 40): operator + compute/credentials/applications/
  #   actions/accounts/networks/sessions.* (compute = no delete)
  admin:
    inherit: operator
    extras:
      # compute.* — Crossplane compose.openova.io claims (no delete; that
      # stays owner-only). Day-2 CRUD via the Composition family.
      - apiGroups: ["compose.openova.io"]
        resources:
          - clusterclaims
          - regionclaims
          - nodepoolclaims
          - loadbalancerclaims
          - peeringclaims
          - nodeactionclaims
        verbs: [create, get, list, watch, update, patch]
      # credentials.* — Secrets full CRUD (the line separating editor from
      # admin in the existing application-* ClusterRoles).
      - apiGroups: [""]
        resources: [secrets]
        verbs: [create, get, list, watch, update, patch, delete]
      # applications.* — Application CR + HelmRelease/Kustomization.
      - apiGroups: ["apps.openova.io"]
        resources: [applications]
        verbs: [create, update, patch, delete]
      - apiGroups: ["helm.toolkit.fluxcd.io"]
        resources: [helmreleases]
        verbs: [create, get, list, watch, update, patch, delete]
      - apiGroups: ["kustomize.toolkit.fluxcd.io"]
        resources: [kustomizations]
        verbs: [create, get, list, watch, update, patch, delete]
      # actions.* — operator-triggered actions (Runbook executions).
      - apiGroups: ["catalyst.openova.io"]
        resources: [runbooks, runbookexecutions]
        verbs: [create, get, list, watch, update, patch, delete]
      # accounts.* — Organization sub-account management.
      - apiGroups: ["orgs.openova.io"]
        resources: [organizations, organizations/status]
        verbs: [get, list, watch, update, patch]
      # networks.* — Cilium policies + Service mesh.
      - apiGroups: ["cilium.io"]
        resources: [ciliumnetworkpolicies, ciliumclusterwidenetworkpolicies]
        verbs: [create, get, list, watch, update, patch, delete]
      # sessions.* — full CRUD on recorded sessions.
      - apiGroups: ["catalyst.openova.io"]
        resources: [sessions]
        verbs: [create, update, patch, delete]
      # workloads.* — full Deployment/StatefulSet write access at admin.
      - apiGroups: ["apps"]
        resources: [deployments, statefulsets, daemonsets, replicasets]
        verbs: [create, update, patch, delete]
      - apiGroups: ["apps"]
        resources: [deployments/scale, statefulsets/scale]
        verbs: [get, patch, update]
      - apiGroups: ["batch"]
        resources: [jobs, cronjobs]
        verbs: [create, update, patch, delete]
      - apiGroups: ["networking.k8s.io"]
        resources: [ingresses, networkpolicies]
        verbs: [create, update, patch, delete]
      - apiGroups: [""]
        resources: [pods, services, configmaps, persistentvolumeclaims, serviceaccounts]
        verbs: [create, update, patch, delete]

  # ── tier-owner (level 50): admin + rbac.* + organization.*
  # Owners can grant other owners — DO NOT delete this without an
  # explicit Org-handover migration story.
  owner:
    inherit: admin
    extras:
      # rbac.* — full RBAC management (UserAccess CR + RoleBindings).
      - apiGroups: ["rbac.authorization.k8s.io"]
        resources: [roles, rolebindings, clusterroles, clusterrolebindings]
        verbs: [create, get, list, watch, update, patch, delete]
      - apiGroups: ["access.openova.io"]
        resources: [useraccesses, useraccesses/status]
        verbs: [create, get, list, watch, update, patch, delete]
      # organization.* — Org CRD CRUD (delete only at owner tier).
      - apiGroups: ["orgs.openova.io"]
        resources: [organizations]
        verbs: [create, delete]
      # compute.delete — owners can delete cloud resources too. Listed
      # here separately so the audit log distinguishes admin (no delete)
      # from owner (delete).
      - apiGroups: ["compose.openova.io"]
        resources:
          - clusterclaims
          - regionclaims
          - nodepoolclaims
          - loadbalancerclaims
          - peeringclaims
          - nodeactionclaims
        verbs: [delete]

# A3 — UserAccess boundary validator (EPIC-3 #1098). Kyverno
# ClusterPolicy at templates/policies/userAccess-boundary.yaml denies
# cross-Organization UserAccess grants unless the requester is a
# member of the management Org with tier=owner.
#
# Default Audit (permissive) so the rule surfaces violations to the
# UI's policy report without blocking the apply. Per-Sovereign overlay
# flips to Enforce when an Org owner is ready to harden the boundary.
#
# allowedCrossOrgTiers: realm-role names whose holder may sign a
# cross-Org grant. The default is `owner` (matches the EPIC-3 T2
# Keycloak realm role `catalyst-owner`); when corporate Sovereigns
# delegate cross-Org signing to a partner SI, append the SI's tier
# realm role here.
userAccessBoundary:
  enabled: true
  action: Audit
  allowedCrossOrgTiers:
    - owner
    - catalyst-owner
