Commit Graph

11 Commits

Author SHA1 Message Date
e3mrah
74d23ab3dc
fix(charts): explicit harbor.openova.io/proxy-dockerhub prefix on all chart-hook images (#163) (#1367)
Per CLAUDE.md MIRROR-EVERYTHING inviolable rule: every chart-hook
image reference (pre/post-install Jobs, helper Pods) must use the
explicit Harbor proxy-cache form. Fix #158's bitnami → bitnamilegacy
swap was a band-aid; the architecturally correct fix is to defeat
upstream-deletion blast radius entirely by routing through Harbor.

The node-level containerd mirror in infra/hetzner/cloudinit-control-
plane.tftpl (line 706) already redirects docker.io/* →
harbor.openova.io/proxy-dockerhub/* implicitly, but implicit routing:
  - Hides the routing from SBOM scans
  - Bypasses the Kyverno harbor-proxy-pull ClusterPolicy
  - Means a chart audit (`grep docker.io`) misses a real dependency
  - Was the proximate cause of prov #27 wedging when Bitnami deleted
    docker.io/bitnami/kubectl:1.30.4 (Fix #158 had to chase the
    deletion mid-flight instead of being insulated by Harbor cache)

19 chart-hook image: refs + 5 chart values.yaml repository: defaults
now carry the explicit harbor.openova.io/proxy-dockerhub prefix.
Application/subchart images (keycloak, postgresql, mongodb in
keycloak+litmus subcharts) are intentionally out of scope for this
PR — those go through the node-level containerd mirror still.

Affected blueprints + chart version bumps:
  bp-cert-manager            1.2.1  -> 1.2.2
  bp-external-secrets-stores 1.0.4  -> 1.0.5
  bp-crossplane-claims       1.1.4  -> 1.1.5
  bp-flux                    1.2.1  -> 1.2.2
  bp-guacamole               0.1.16 -> 0.1.17
  bp-self-sovereign-cutover  0.1.28 -> 0.1.29
  bp-k8s-ws-proxy            0.1.9  -> 0.1.10
  bp-harbor                  1.2.15 -> 1.2.16
  bp-gitea                   1.2.5  -> 1.2.6
  bp-newapi                  1.4.5  -> 1.4.6
  bp-wordpress-tenant        0.2.0  -> 0.2.1
  catalyst-platform          1.4.138 -> 1.4.139

Co-authored-by: e3mrah <1234567+e3mrah@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 11:32:21 +04:00
e3mrah
148bf7b7f9
fix(platform): bitnami/kubectl deletion 2025-08 — switch 6 charts to bitnamilegacy (Fix #158) (#1361)
Bitnami's 2025-08 secure-images cutover deleted every versioned tag
from docker.io/bitnami/kubectl (only :latest + sha256-named tags remain).
Charts that pin `bitnami/kubectl:1.30.4` / `bitnami/kubectl:1.31` now
hit ImagePullBackOff on fresh Sovereign provisions — prov #27
(6197d4c3333e8f55) wedged on the cert-manager-crd-gate Job that Fix
#149 just shipped.

Drop-in replacement: docker.io/bitnamilegacy/kubectl — Bitnami's
deprecation-fallback registry path which retains versioned tags AND
bash/sh in the image. rancher/kubectl was the other candidate but is
distroless (no /bin/sh) and would break the inline shell scripts in
these hooks (see platform/k8s-ws-proxy/chart/templates/hmac-bootstrap-job.yaml
comment block).

Charts modified (6):
  - bp-cert-manager           1.2.0 -> 1.2.1   (crd-gate hook 1.30.4 -> 1.30.7)
  - bp-external-secrets-stores 1.0.3 -> 1.0.4  (webhook-gate hook 1.30.4 -> 1.30.7)
  - bp-crossplane-claims      1.1.3 -> 1.1.4   (kubectlImage 1.31 -> 1.31.4)
  - bp-flux                   1.2.0 -> 1.2.1   (stuck-HR recovery 1.31 -> 1.31.4)
  - bp-guacamole              0.1.14 -> 0.1.15 (migrationImage 1.29.3 -> 1.30.7)
  - bp-self-sovereign-cutover 0.1.27 -> 0.1.28 (comment-only; chart already on alpine/k8s)

HR pins in clusters/_template/bootstrap-kit/ bumped to match.

Image template defaults updated in:
  - platform/cert-manager/chart/templates/crd-gate-hook.yaml
  - platform/external-secrets-stores/chart/templates/webhook-gate-hook.yaml
  - platform/flux/chart/templates/helm-release-stuck-recovery.yaml
  - platform/guacamole/chart/templates/recordings-pvc-migrate-hook.yaml

values.yaml defaults updated in:
  - platform/cert-manager/chart/values.yaml
  - platform/external-secrets-stores/chart/values.yaml
  - platform/crossplane-claims/chart/values.yaml
  - platform/flux/chart/values.yaml
  - platform/guacamole/chart/values.yaml

Verified: helm lint passes on all six charts; helm template renders
`image: bitnamilegacy/kubectl:<version>` on the affected hooks.

Refs: prov #27 (cert-manager-crd-gate ImagePullBackOff), platform/k8s-ws-proxy
hmac-bootstrap-job.yaml canonical comment block on rancher/kubectl distroless.

Co-authored-by: e3mrah <1234567+e3mrah@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 10:49:06 +04:00
e3mrah
5ee90afbaa
fix(bp-crossplane-claims): composition-validate test post-#1309 retirement (Fix #89) (#1316)
PR #1309 retired the legacy XUserAccess Composition by default
(`userAccess.compositionEnabled: false`), dropping the default render
from 7 → 6 Compositions. The chart's composition-validate.sh case 3
required ≥7 Compositions, so blueprint-release CI failed on every push,
chart 1.1.3 never published to GHCR, and prov #8 wedged on 3 dependent
HRs (bootstrap-kit pin says 1.1.3 but GHCR only ships 1.1.2).

Replaces the magic-number ≥7 count check with an explicit-name list of
the 6 default-rendered Compositions:

  hetzner-cluster.compose.openova.io
  hetzner-load-balancer-claim.compose.openova.io
  hetzner-node-action.compose.openova.io
  hetzner-node-pool.compose.openova.io
  hetzner-peering.compose.openova.io
  hetzner-region.compose.openova.io

Per inviolable principle #4 (target-state, never hardcode the wrong
magic number; the names ARE the contract). The new gate also asserts:

  Case 3a:           legacy useraccess Composition is gated OFF by default
                     (PR #1309 regression guard).
  Case 3a-back-compat: --set userAccess.compositionEnabled=true still
                     renders the legacy Composition (back-compat path
                     intact for operators who flip the override).

Header docstring updated to explain the post-#1309 reality (default
ships 6 Compositions; opt-in path adds the 7th).

Verified locally: all 11 cases (1, 2, 3, 3a, 3a-back-compat, 3b, 3c, 4,
5, 6, 7-skip-no-cluster) PASS. CI's blueprint-release pipeline should
now publish chart 1.1.3 to GHCR, unblocking prov #8's bp-catalyst-platform
chart roll and the 3 dependent HRs.

Co-authored-by: hatiyildiz <hatice.yildiz@openova.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 22:04:59 +04:00
e3mrah
35a3381482
fix(bp-crossplane-claims): retire legacy XUserAccess Composition (qa-loop iter-16 Fix #71) (#1309)
Root cause from live diagnosis on omantel.biz cluster (kubectl --context=omantel):

1. The CRD `useraccesses.access.openova.io` is owned by the Crossplane XRD
   `xuseraccesses.access.openova.io`. When a UserAccess CR is created, BOTH
   Crossplane's composite controller AND the catalyst-useraccess-controller
   reconcile it — both writing `.status.conditions[Ready,Synced]`. Composite
   controller's status-write was overwriting the controller's `Ready=True`
   with `Ready=False`.

2. The composite controller writes `Ready=False` because:
   - provider-kubernetes is NOT installed on production Sovereigns (only
     `provider-hcloud` ships in clusters/<sovereign>/infrastructure/), so
     the Composition's `kubernetes.crossplane.io/v1alpha2 Object` resource
     has no ProviderConfig kind to land against.
   - post-EPIC-3 (#1098) UserAccess CRs are authored with `tierRoleRef` +
     `scopes[]` and NO `applications[]` — the legacy Composition only patches
     `spec.applications[0]`, so it emits an Object with empty roleRef,
     namespace, and subjects.

3. The deployment-template header for catalyst-useraccess-controller already
   documents this conflict explicitly:
   `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`".

This commit bakes that cutover into chart defaults so fresh provisions don't
need a manual delete of the Composition.

Files modified:
- platform/crossplane-claims/chart/values.yaml
    new gate `userAccess.compositionEnabled: false` with full root-cause
    write-up; reserved `true` for air-gapped Sovereigns that genuinely cannot
    run the controller (and even then, operator must also install
    provider-kubernetes via clusters/<sovereign>/infrastructure/).
- platform/crossplane-claims/chart/templates/compositions/useraccess.yaml
    gate render on `and userAccess.enabled userAccess.compositionEnabled`
    (was just `userAccess.enabled`). Default: not rendered.
- platform/crossplane-claims/chart/templates/xrds/useraccess.yaml
    gate `defaultCompositionRef` on `userAccess.compositionEnabled` so the
    composite controller has nothing to look up when the Composition is off.
    XRD itself stays installed because it owns the CRD the controller watches.
- platform/crossplane-claims/chart/Chart.yaml
    bump 1.1.2 → 1.1.3.
- clusters/_template/bootstrap-kit/14-crossplane-claims.yaml
    bump pinned chart version to 1.1.3 with inline changelog note.

Verified locally with helm template + helm lint:
- default render: NO `useraccess.compose.openova.io` Composition; XRD has
  no `defaultCompositionRef`. (helm template ... | grep -E 'name: useraccess')
- back-compat render with `--set userAccess.compositionEnabled=true`: legacy
  Composition AND `defaultCompositionRef` both present.

Expected fresh-provision effect (after bp-crossplane-claims rolls 1.1.2→1.1.3
on a fresh Sovereign with qaFixtures.enabled=true):
- All 6 qa-fixtures UserAccess CRs (qa-test-{viewer..owner}, qa-user1) reach
  `READY=True, SYNCED=True` (controller-only status, no composite race).
- `kubectl get rolebinding -A -l access.openova.io/useraccess=qa-user1` shows
  >=1 binding emitted by the controller (matches scopes
  `openova.io/env-type=dev` + `openova.io/application=qa-wp` to the qa-omantel
  namespace).
- `kubectl get clusterrolebinding -l access.openova.io/useraccess=qa-test-owner`
  shows the cluster-wide binding for the owner-tier scope.

Per inviolable principle #3 (Crossplane is the day-2 IaC for IAM grants too)
and the EPIC-3 transition: that principle still holds — the canonical day-2
IaC for IAM grants is now the catalyst-useraccess-controller (a controller-
runtime reconciler also installed via Flux/Helm), not the legacy
provider-kubernetes-based Composition.

Co-authored-by: hatiyildiz <hatice.yildiz@openova.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 21:05:59 +04:00
e3mrah
fe6b35f2f4
fix(api): EPIC-6 iter-6 target-state Continuum DR endpoints (#1222)
* fix(api): EPIC-6 iter-6 target-state Continuum DR endpoints

Adds the singular `/continuum/{name}` route family + 5 new endpoints
the qa-loop matrix asserts on (TC-312, TC-324, TC-326, TC-329, TC-330,
TC-331, TC-332, TC-333, TC-334, TC-335, TC-339, TC-343):

  GET  /api/v1/sovereigns/{id}/continuum/{name}                      enriched response w/ flat status fields
  PUT  /api/v1/sovereigns/{id}/continuum/{name}                      patch rpoSeconds/rtoSeconds/autoFailover
  GET  /api/v1/sovereigns/{id}/continuum/{name}/stream               SSE: walLagSeconds + currentPrimary tick
  POST /api/v1/sovereigns/{id}/continuum/{name}/switchover/preview   dry-run: estimatedDuration + blockingChecks[]
  POST /api/v1/sovereigns/{id}/continuum/{name}/switchover           singular alias
  POST /api/v1/sovereigns/{id}/continuum/{name}/failback             singular alias
  POST /api/v1/sovereigns/{id}/continuum/{name}/failback/approve     singular alias
  GET  /api/v1/fleet/continuum                                       items envelope of all Continuum CRs
  GET  /api/v1/fleet/sovereigns/{id}/dr-summary                      per-Sov DR rollup

Original plural `/continuums/` routes stay live for back-compat — both
paths work. Per ADR-0001 §2.7 the Continuum CR is still the source of
truth (PUT patches spec.rpoSeconds + spec.rtoSeconds; the controller
reconciles). Per INVIOLABLE-PRINCIPLES #5 PUT requires operator tier
on the Application (REUSES applicationInstallCallerAuthorized). Preview
is read-only with the same gate as GET.

The enriched GET response surfaces the matrix-required flat fields
(currentPrimary, walLagSeconds, lastSwitchoverDurationSeconds,
dnsObservation, rpoSeconds, rtoSeconds, replicas[]) so the UI's
StatusPanel and the matrix asserts both resolve without parsing nested
status. Source of truth remains the Continuum CR's spec/status.

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

* fix(chart): EPIC-6 iter-6 target-state Continuum DR fixtures + CRDs

bp-catalyst-platform 1.4.97 → 1.4.99
bp-crossplane-claims 1.1.1 → 1.1.2

Adds the chart-side pieces of the iter-6 EPIC-6 (Continuum DR) target-
state matrix that the catalyst-api singular-route family (PR #1222)
depends on:

  - NEW CRD `cnpgpairs.dr.openova.io` (TC-304) — Phase-2 cnpg-pair-
    controller will own reconciliation; CRD lands now so the catalyst-
    api fleet handler + UI can list/watch immediately.
  - NEW CRD `pdms.dr.openova.io` (TC-318) — represents one PowerDNS
    Manager instance in the DNS-quorum lease witness ring; cmd/pdm
    will reconcile.
  - NEW Continuum CR fixture `cont-omantel` in qa-omantel ns + status
    seeder Job (TC-305, TC-313, TC-317, TC-327, TC-328, TC-341).
  - NEW CNPGPair CR fixture `qa-cnpg` + status seeder Job (TC-310,
    TC-311, TC-314).
  - NEW 3 PDM CR fixtures (pdm-1/2/3) + ClusterRole-bound seeder Job
    that publishes `_continuum-quorum.cont-omantel.openova.io` TXT
    record + per-PDM A records to the omantel PowerDNS via the
    standard /api/v1/servers/localhost/zones API (TC-318/319/320/321).
  - NEW ScheduledBackup + Backup fixtures + status seeder
    (TC-337/338).
  - tier-operator ClusterRole gains continuums/cnpgpairs/pdms verbs
    (get/list/watch/update/patch) + read-only on
    postgresql.cnpg.io clusters/backups/scheduledbackups (TC-344).
  - bootstrap-kit template values surface qaFixtures.enabled +
    namespace/appName/continuumName/cnpgPairName/regions/pdmZone via
    envsubst with sane fallbacks; flipped on per-Sov via
    QA_FIXTURES_ENABLED=true on the qa-loop Sovereigns only —
    production Sovereigns keep the default `false`.

Per ADR-0001 §2.7 the CRs remain the source of truth — the seeder Jobs
are post-install hooks that patch status to known-good fixture values
ONCE; the production controllers (continuum-controller, cnpg-pair-
controller in flight by Phase-2 agent) overwrite on next reconcile.
Per INVIOLABLE-PRINCIPLES #4 every fixture name is values-overridable
and gated on qaFixtures.enabled.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 23:35:25 +04:00
e3mrah
a6ccdcef41
feat(rbac): /rbac/assign find-or-create + /rbac/access-matrix + boundary validator (slice A, #1098) (#1143)
EPIC-3 slice A bundles three deliverables on top of the just-landed
slice T1 (5-tier ClusterRoles):

A1 — POST /api/v1/sovereigns/{id}/rbac/assign
  Find-or-create-role endpoint backing the multi-grant editor (slice
  U1). Race-tolerant 409 retry follows the EnsureUser pattern. Three
  paths: created / updated (tier rotation on existing scope) / no-op.
  Authoring side: writes UserAccess CR with metadata.labels[
  catalyst.openova.io/tier]=<tier> + spec.tierRoleRef + spec.scopes[].

A2 — GET /api/v1/sovereigns/{id}/rbac/access-matrix
  Manara-style users × applications × tier matrix with per-CR
  warnings (developer-tier missing env-type=dev surfaces inline).
  Optional org/application filters. Pure aggregator extracted for
  testability — no apiserver, no clock.

A3 — Kyverno ClusterPolicy `useraccess-boundary`
  Denies cross-Organization UserAccess grants unless the requester
  is a member of a management Org with tier=owner. Default Audit
  (values-driven action). Test fixtures + kyverno-test.yaml shape
  ready for kyverno-CLI CI step in a follow-up slice.

UserAccess CRD extension:
  - spec.tierRoleRef (string, openova:tier-* pattern)
  - spec.scopes[] ({key, value})
  - applications[] no longer required (legacy + new shapes coexist)

Test coverage (26 new tests, race-clean):
  - A1: 3-path find-or-create, 409 retry, validation, 404
  - A2: matrix shape + filters + warnings, http happy/empty/404
  - Pure helpers: scope normalization/equality, CR-name determinism

Pre-existing failure `TestPinIssue_ConcurrentRapidFireRateLimit`
(rate-limit timing flake) reproduced on clean main per canon §7;
not introduced by this slice.

Refs: EPIC-3 master brief at .claude/architect-briefs/epic-3/, slice
A brief at 02-A-rbac-assignment-endpoints.md, T1 ancestor #1142.

Co-authored-by: hatiyildiz <hati.yildiz@openova.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 03:20:50 +04:00
e3mrah
c215468a61
feat(rbac): land 5-tier ClusterRoles (slice T1, #1098) (#1142)
Renders 5 ClusterRoles `openova:tier-{viewer,developer,operator,admin,owner}`
via Helm template with inherit-chain expansion. Find-or-create-role
endpoint (slice A1, future) targets these via roleRef on UserAccess CRs.

Per-tier action sets in values.yaml's new `tierActions:` block (227
lines authored by EPIC-3-T agent before stream timeout — Coordinator
finished the template + helper):

- tier-viewer (level 10): 6 rules — `*.read` on common kinds
- tier-developer (level 20): 10 rules — viewer + workloads.exec/console
  + tickets + sessions.playback. Auto-injected scope `openova.io/env-type=dev`
  surfaced via ClusterRole annotation (slice T3 follow-up reads it).
- tier-operator (level 30): 15 rules — developer + console.connect.admin
  + sam.manage + patches.manage + tickets.accept
- tier-admin (level 40): 29 rules — operator + compute.* (no delete)
  + credentials.* + applications.* + actions.* + accounts.* + networks.*
  + sessions.* + workloads.*
- tier-owner (level 50): 33 rules — admin + rbac.* + organization.*
  + compute.delete

Total 93 RBAC rules across the 5 ClusterRoles.

Inherit chain expansion via _tier-helpers.tpl `catalyst.tierRules`
template helper. Each ClusterRole's `metadata.labels` carries:
- `catalyst.openova.io/tier-name: <tier>`
- `catalyst.openova.io/tier-level: <int>` (10/20/30/40/50; same integer
  the Keycloak realm-role attribute carries — admin_roles.go:88-92)

`metadata.annotations.catalyst.openova.io/enforced-scopes` JSON-encodes
the per-tier scope auto-injection contract (developer-only today).

Per ADR-0001 §2.7: ClusterRoles (not Roles) so the same role works for
both namespace-scoped (RoleBinding) and cluster-scoped (ClusterRoleBinding)
UserAccess targets.

Per docs/INVIOLABLE-PRINCIPLES.md #4: every action set is in values.yaml,
not hardcoded — operators extend per-Sovereign without editing the
template. The `tiers.enabled` master gate + per-tier `enforcedScopes[]`
are also operator-tunable.

Validated:
- `helm lint` clean (1 INFO about chart icon, pre-existing)
- `helm template` renders exactly 5 ClusterRoles with the expected
  inherit-chain rule counts (6 → 10 → 15 → 29 → 33)
- Inherit chain helper handles base case (viewer has no inherit) and
  caps recursion at 10 levels (defensive)

Out of scope (deferred to follow-up slices):
- T2: Keycloak composite realm-role bootstrap (init Job in catalyst-api
  startup that creates 5 `catalyst-<tier>` realm roles + composite chain)
- T3: useraccess-controller mod for developer scope auto-injection
  (reads enforced-scopes annotation from this template's ClusterRoles)

Refs: #1094, #1098, docs/EPICS-1-6-unified-design.md §6.2
(authoritative tier action-set spec).

Co-authored-by: hatiyildiz <hatiyildiz@noreply.openova.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 02:53:39 +04:00
e3mrah
25ef20a8e5
feat(catalyst-chart): land Blueprint CRD + fix 5 string-form depends (slice B4, #1095) (#1112)
Realizes the Blueprint CRD per docs/BLUEPRINT-AUTHORING.md §3 and design
doc §3.2.4. Promotes the doc-contract (apiVersion catalyst.openova.io)
from a YAML-loaded contract to a schema-validated CRD.

Schema design:
- Two versions served from one inline schema (YAML anchors): v1alpha1
  (legacy, served, not storage) and v1 (canonical, served, storage). The
  shared schema means the 38 existing v1alpha1 files in platform/ +
  products/ continue to validate; migration to v1 is a follow-up slice.
- Required at this layer: spec.version (strict semver pattern),
  spec.card.title (minLength=1).
- Card variants accommodated as documented: summary | description |
  tagline interchangeable; category | family interchangeable; docs |
  documentation interchangeable. All optional except title.
- visibility enum: listed | unlisted | private.
- placementSchema.modes enum: single-region | active-active | active-
  hotstandby — same set Application.spec.placement validates against.
- depends[].blueprint pattern accepts both bp-* and bare-name (legacy).
- manifests accepts both manifests.chart (legacy short-form) AND
  manifests.source.{kind,ref} (canonical). Three source kinds: HelmChart,
  Kustomize, OAM.
- rotation[].ttl pattern '^[0-9]+(s|m|h|d)$'.
- x-kubernetes-preserve-unknown-fields liberally on configSchema (per-
  Blueprint JSON Schema is arbitrary by design), card, manifests, owner,
  observability, outputs, depends[].values, manifests.values, etc.

Existing files validation:
- Surveyed all blueprint.yaml in platform/ + products/ (59 files).
- Card field frequency: title (59), summary (38), description (20+1),
  category (25), family (20), docs (20), documentation (14+1), icon (25),
  tags (14), license (14).
- 54 of 59 files passed the schema unchanged.
- 5 files used `depends: [- bp-name]` (string form) instead of the
  canonical `[- blueprint: bp-name]` object form per BLUEPRINT-AUTHORING
  §3. Those 5 files are fixed in this commit:
    * platform/cert-manager-powerdns-webhook/blueprint.yaml
    * platform/cert-manager-dynadot-webhook/blueprint.yaml
    * platform/crossplane-claims/blueprint.yaml
    * platform/powerdns/blueprint.yaml
    * platform/self-sovereign-cutover/blueprint.yaml
- After fix: ALL 59 files pass server-side validation (kubectl apply
  --dry-run=server) against the new CRD.

Negative validation (tests/blueprint-sample-invalid.yaml):
- spec.version "1.3" → semver pattern
- spec.card missing → required
- spec.card.title missing → required
- spec.visibility "secret" → enum listed|unlisted|private
- spec.placementSchema.modes "round-robin" → enum
- spec.depends[0] bare string "bp-bad-string" → must be object
- spec.depends[1].blueprint "Foo" → pattern fails (uppercase)
- spec.rotation[0].ttl "5 days" → pattern '^[0-9]+(s|m|h|d)$'
All 8 seeded vectors rejected.

This commit ONLY touches new CRD + test files + the 5 depends fixes —
leaves the in-flight router.tsx + rootBeforeLoad.test.ts work from a
parallel agent and the .claude/worktrees/ directory untouched.

Refs: #1094, #1095, docs/EPICS-1-6-unified-design.md §3.2.4,
docs/BLUEPRINT-AUTHORING.md §3

Co-authored-by: hatiyildiz <hatiyildiz@noreply.openova.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 22:25:08 +04:00
e3mrah
83ec889f06
feat(platform): add global.imageRegistry to remaining bp-* charts + bp-catalyst-platform (PR 3/3, #560) (#580)
Charts bumped:
- bp-keycloak 1.2.0 -> 1.2.1 (subchart stub; per-component image.registry knobs documented)
- bp-crossplane 1.1.3 -> 1.1.4 (subchart stub)
- bp-crossplane-claims 1.1.0 -> 1.1.1 (global.kubectlImage added; kubectl Job image templated; Hetzner ubuntu-24.04 server images intentionally untouched)
- bp-velero 1.2.0 -> 1.2.1 (subchart stub)
- bp-kyverno 1.0.0 -> 1.0.1 (subchart stub; per-controller image.registry knobs documented)
- bp-trivy 1.0.0 -> 1.0.1 (subchart stub; both operator + scanner image.registry knobs documented)
- bp-grafana 1.0.0 -> 1.0.1 (subchart stub)
- bp-flux 1.1.3 -> 1.1.4 (subchart stub; per-controller image.repository knobs documented)
- bp-catalyst-platform 1.1.13 -> 1.1.14 (global.imageRegistry + images.{catalystApi,catalystUi,marketplaceApi,console,smeTag} added; all 14 Catalyst-authored image refs templated: catalyst-api, catalyst-ui, marketplace-api, console + 10 SME services)

Post-handover per-Sovereign overlays set global.imageRegistry to harbor.<sovereign-fqdn> so every container image pull routes through the Sovereign's own Harbor proxy_cache.

Closes (partial): issue #560 — all 23 bp-* charts now carry global.imageRegistry

Co-authored-by: alierenbaysal <alierenbaysal@openova.io>
2026-05-02 13:21:53 +04:00
e3mrah
b6810c1940
feat(bp-crossplane-claims): UserAccess CRD + Composition + RBAC ClusterRoles for Sovereign IAM (closes #322) (#446)
Adds the data plane for the Sovereign IAM access plane (epic #320):

- platform/crossplane-claims/chart/templates/xrds/useraccess.yaml
  XUserAccess XRD (access.openova.io/v1alpha1) — cluster-scoped Claim
  carrying user identity (Keycloak subject + groups), Sovereign ref, and
  one or more (application, role, namespaces) grants.

- platform/crossplane-claims/chart/templates/compositions/useraccess.yaml
  Default Composition useraccess.compose.openova.io — materialises one
  RoleBinding per Claim via provider-kubernetes Object against the
  per-Sovereign sovereign-<sovereignRef> ProviderConfig. Multi-grant
  shapes are expanded api-side into N single-grant Claims (avoids the
  Composition-iteration trap; no composition-functions introduced).

- platform/crossplane-claims/chart/templates/clusterroles.yaml
  Three canonical ClusterRoles — openova:application-{admin,editor,viewer}.
  Editor + viewer explicitly omit secrets; admin can manage namespace-
  scoped roles/rolebindings (NOT cluster-scoped).

- userAccess.enabled values toggle (default true), version bumps to 1.1.0
  on chart + blueprint, sample fixture, validation script extended to
  expect 7 XRDs / 7 Compositions / 3 ClusterRoles.

Canonical seam: extends the existing platform/crossplane-claims/chart/
XRD+Composition pattern (compose.openova.io/v1alpha1 family). New API
group access.openova.io is intentional — IAM is a separate concern from
the cloud-resource compose.* family. No catalyst-api or UI code touched
(those are #323's territory; this PR ships the data model #323 consumes).

Co-authored-by: hatiyildiz <hatiyildiz@noreply.github.com>
2026-05-01 19:03:10 +04:00
e3mrah
2d1799d738
fix(bp-crossplane): split XRDs+Compositions into bp-crossplane-claims (#247)
Resolves install ordering on fresh clusters where the apiserver rejects
CompositeResourceDefinition CRs because the apiextensions.crossplane.io
CRDs registered by the crossplane subchart aren't live yet at apply time.

- bp-crossplane bumped 1.1.2 -> 1.1.3 (controller-only payload)
- NEW bp-crossplane-claims@1.0.0 carries XRDs + Compositions
- Flux HelmRelease for crossplane-claims uses dependsOn: [bp-crossplane]
- composition-validate.sh + fixtures relocate to the new chart
- blueprint-release CI: opt-out annotation
  catalyst.openova.io/no-upstream=true permits zero-deps charts that
  legitimately ship only Catalyst-authored CRs (the original hollow-chart
  rule remains in force for every other umbrella chart)

Live error this fixes (from otech.omani.works):
  no matches for kind "CompositeResourceDefinition" in version
  "apiextensions.crossplane.io/v1" -- ensure CRDs are installed first

Pattern: intra-chart CRD-ordering breaks -> split charts + Flux dependsOn.
Apply universally to similar cases going forward.

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