Skip to content

fix(module): add missing watch verb on services and prometheuses RBAC - #913

Merged
kpunwatk merged 2 commits into
trustyai-explainability:mainfrom
kpunwatk:fix/module-services-watch-rbac
Sep 11, 2026
Merged

kpunwatk merged 2 commits into
trustyai-explainability:mainfrom
kpunwatk:fix/module-services-watch-rbac

Conversation

@kpunwatk

@kpunwatk kpunwatk commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor

Same root cause as #908 (deployments, rolebindings): the manager's cached client establishes an informer/watch for any GVK it Lists or Gets, requiring the watch verb in addition to get/list. Two more call sites hit this that weren't covered by #908:

  • migration.go's adoptServices lists Services for in-tree adoption
  • dependencies.go's checkPrometheus lists Prometheus via the cached client (the InferenceService check uses RESTMapper discovery instead, so it doesn't need watch)

Found by proactively auditing every List/Get call site in the package after Steven Tobin reported the Services watch failure, to catch the remaining latent gap before it surfaced separately.

Related to opendatahub-io/opendatahub-operator#4051 — Steven is testing
the TrustyAI module end-to-end via that PR, and this fixes one of the
errors he's hit there.

Summary by CodeRabbit

  • Bug Fixes
    • Improved monitoring and service resource tracking for more timely operator updates.
    • Updated permissions to support watching Prometheus and service resources alongside existing access.

Same root cause as trustyai-explainability#908 (deployments, rolebindings): the manager's
cached client establishes an informer/watch for any GVK it Lists or
Gets, requiring the watch verb in addition to get/list. Two more call
sites hit this that weren't covered by trustyai-explainability#908:

- migration.go's adoptServices lists Services for in-tree adoption
- dependencies.go's checkPrometheus lists Prometheus via the cached
  client (the InferenceService check uses RESTMapper discovery
  instead, so it doesn't need watch)

Found by proactively auditing every List/Get call site in the package
after Steven Tobin reported the Services watch failure, to catch the
remaining latent gap before it surfaced separately.
@coderabbitai

coderabbitai Bot commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

Warning

Review limit reached

Next included review available in 37 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 53721c95-7fa6-45f4-91f3-6ff624c215c9

📥 Commits

Reviewing files that changed from the base of the PR and between 97c8356 and 64b0da1.

📒 Files selected for processing (2)
  • policy/clusterrole.rego
  • policy/clusterrole_test.rego

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 5cc2f5bf-912e-47b5-9c1e-c55025d39dbd

📥 Commits

Reviewing files that changed from the base of the PR and between 8e502a6 and 97c8356.

📒 Files selected for processing (2)
  • trustyai-operator-module/config/rbac/role.yaml
  • trustyai-operator-module/pkg/trustyaimodule/reconciler.go

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The operator RBAC declarations now grant watch access to core services and monitoring.coreos.com prometheuses. The generated ClusterRole and kubebuilder markers contain the updated permissions.

Changes

RBAC watch permissions

Layer / File(s) Summary
Add watch permissions
trustyai-operator-module/pkg/trustyaimodule/reconciler.go, trustyai-operator-module/config/rbac/role.yaml
The RBAC markers and generated ClusterRole add watch for services and prometheuses. Existing permissions remain unchanged.

Estimated code review effort: 1 (Trivial) | ~2 minutes

Merge Risk: ⚪ Minimal · up to 97c83

This enables the operator's cached Service and Prometheus resource access without adding unrelated permissions, leaving no actionable merge-blocking risk.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 1…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding the missing watch verb for Services and Prometheuses RBAC.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@ruivieira ruivieira left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @kpunwatk LGTM.
Is it ok just to add the rego tests, eg (to policy/clusterrole.rego):

  deny contains msg if {
        input.kind == "ClusterRole"
        input.metadata.name == "trustyai-operator-module-manager-role"

        rule := input.rules[_]
        "" in rule.apiGroups
        "services" in rule.resources
        not "watch" in rule.verbs

        msg := "RBAC VIOLATION: TrustyAI module services permission must include watch."
  }

  deny contains msg if {
        input.kind == "ClusterRole"
        input.metadata.name == "trustyai-operator-module-manager-role"

        rule := input.rules[_]
        "monitoring.coreos.com" in rule.apiGroups
        "prometheuses" in rule.resources
        not "watch" in rule.verbs

        msg := "RBAC VIOLATION: TrustyAI module prometheuses permission must include watch."
  }

and to (policy/clusterrole_test.rego)

  test_module_services_watch_required if {
        count(deny) > 0 with input as {
                "kind": "ClusterRole",
                "metadata": {"name": "trustyai-operator-module-manager-role"},
                "rules": [{
                        "apiGroups": [""],
                        "resources": ["services"],
                        "verbs": ["get", "list", "patch"],
                }],
        }
  }

  test_module_prometheuses_watch_required if {
        count(deny) > 0 with input as {
                "kind": "ClusterRole",
                "metadata": {"name": "trustyai-operator-module-manager-role"},
                "rules": [{
                        "apiGroups": ["monitoring.coreos.com"],
                        "resources": ["prometheuses"],
                        "verbs": ["get", "list"],
                }],
        }
  }

Per Rui Vieira's review on trustyai-explainability#913: add deny rules to policy/clusterrole.rego
that fail if the trustyai-operator-module-manager-role ClusterRole ever
loses the watch verb on services or prometheuses, plus unit tests
covering both the violation and the passing case. Also adds
monitoring.coreos.com/prometheuses to the Layer 1 allowlist, which was
missing despite the module already having get/list on it.

Note: trustyai-operator-module isn't yet one of the kustomize entry
points make policy-check renders (only the in-tree config/overlays/*
paths are), so these rules currently only run via the unit tests in
clusterrole_test.rego, not against the module's real rendered manifests.
Wiring the module into the live policy-check workflow is a separate,
larger change.
@openshift-ci openshift-ci Bot removed the lgtm label Sep 10, 2026
@kpunwatk

Copy link
Copy Markdown
Contributor Author

64b0da1

Done @ruivieira addressed in 64b0da1

@openshift-ci openshift-ci Bot added the lgtm label Sep 10, 2026
@kpunwatk

Copy link
Copy Markdown
Contributor Author

/test images

1 similar comment
@kpunwatk

Copy link
Copy Markdown
Contributor Author

/test images

@openshift-ci

openshift-ci Bot commented Sep 11, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: ruivieira, sheltoncyril

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@kpunwatk
kpunwatk merged commit 2c38de9 into trustyai-explainability:main Sep 11, 2026
12 checks passed
@kpunwatk kpunwatk self-assigned this Sep 11, 2026
kpunwatk added a commit to kpunwatk/opendatahub-operator that referenced this pull request Sep 11, 2026
…(services/prometheuses watch)

trustyai-explainability/trustyai-service-operator#913 merged and has
propagated through both red-hat-data-services sync hops (main, then
rhoai-3.6-ea.2). Bumped the rhoai ref and verified locally: services
and prometheuses RBAC rules now include watch.

The odh pin is not bumped yet -- opendatahub-io/trustyai-service-operator#195
(the pull[bot] sync PR) already has the right content but hasn't merged.
ruivieira pushed a commit that referenced this pull request Sep 13, 2026
…y condition aggregation (#915)

* fix(module): add missing RBAC for serviceaccounts

Same root cause as #908/#913: the module's generic deploy pipeline
looks up the pre-existing in-tree trustyai-service-operator
ServiceAccount before applying the workload manifests, but the
ClusterRole never granted any permission on serviceaccounts at all.

Found via a live cluster test: after fixing the /opt/manifests
permission issue and confirming it resolved, the reconciler
progressed further and hit this as the next blocker:

  serviceaccounts "trustyai-service-operator" is forbidden: User
  "system:serviceaccount:opendatahub:trustyai-operator-module-controller-manager"
  cannot get resource "serviceaccounts" in API group "" in the
  namespace "opendatahub"

* fix(module): add create verb for services, serviceaccounts, deployments, rolebindings

Found via the same live test: after fixing the missing serviceaccounts
RBAC entirely, the next error was "cannot create resource
serviceaccounts" rather than "cannot get". Kubernetes' Server-Side
Apply requires the create verb (not just patch) when the target
object does not already exist yet -- true for these four resource
types on a fresh deploy (new namespace, nothing pre-adopted from an
in-tree controller). patch alone was only sufficient in scenarios
where the object already existed (e.g. adopting from a prior in-tree
install), which is why this wasn't caught until testing a genuinely
fresh deployment.

* fix(module): exempt Degraded condition from happy-condition aggregation

The shared odh-platform-utilities conditions.Manager treats any dependent
condition with Status=False (or Unknown) as "unhappy" and overwrites the
Ready condition's values with it, unless the dependent carries
Severity=Info. Degraded is an inverted-polarity condition (False means
healthy), so every healthy-case MarkFalse(Degraded, "FullyFunctional")
call was clobbering the correct MarkTrue(Ready, "AllServicesHealthy")
set moments earlier in the same reconcile. Mark all Degraded transitions
with WithSeverity(ConditionSeverityInfo) to exempt it from aggregation,
matching the existing precondition.WithSeverity(Info) pattern already
used for the optional KServe dependency check.

Found via live-cluster testing on tai-kpunwatk while replicating
Steven's opendatahub-operator PR #4051 test flow.

* fix(module): wire spec.enabledServices into --enable-services on the deployed operator

spec.EnabledServices was only ever read to decide which per-service
Deployments to health-check; nothing wrote it into the deployed
trustyai-service-operator workload's actual startup configuration. The
static manifests-template Deployment has no --enable-services arg or
ENABLED_SERVICES env var at all, so the underlying operator pod always
crash-looped with "please specify at least one service", regardless of
what the module CR requested.

Add injectEnabledServices, called in reconcileComponent after rendering
the Kustomize overlay and before SSA-applying it, which sets
--enable-services=<comma-separated names> on the manager container of
the trustyai-service-operator Deployment. When no services are
explicitly enabled (the current always-empty state, since
opendatahub-operator's BuildModuleCR does not yet project per-service
toggles onto this CR), default to enabling all five services, matching
the pre-modular in-tree operator's own default deployment args.

Found via live-cluster testing on tai-kpunwatk: with enabledServices
left at its zero value, updateHealthStatus ran zero health checkers and
trivially reported Ready=True while the core operator pod was
crash-looping the entire time.
ruivieira added a commit that referenced this pull request Sep 13, 2026
…operator on fresh installs (#918)

* fix(module): add missing RBAC for serviceaccounts

Same root cause as #908/#913: the module's generic deploy pipeline
looks up the pre-existing in-tree trustyai-service-operator
ServiceAccount before applying the workload manifests, but the
ClusterRole never granted any permission on serviceaccounts at all.

Found via a live cluster test: after fixing the /opt/manifests
permission issue and confirming it resolved, the reconciler
progressed further and hit this as the next blocker:

  serviceaccounts "trustyai-service-operator" is forbidden: User
  "system:serviceaccount:opendatahub:trustyai-operator-module-controller-manager"
  cannot get resource "serviceaccounts" in API group "" in the
  namespace "opendatahub"

* fix(module): add create verb for services, serviceaccounts, deployments, rolebindings

Found via the same live test: after fixing the missing serviceaccounts
RBAC entirely, the next error was "cannot create resource
serviceaccounts" rather than "cannot get". Kubernetes' Server-Side
Apply requires the create verb (not just patch) when the target
object does not already exist yet -- true for these four resource
types on a fresh deploy (new namespace, nothing pre-adopted from an
in-tree controller). patch alone was only sufficient in scenarios
where the object already existed (e.g. adopting from a prior in-tree
install), which is why this wasn't caught until testing a genuinely
fresh deployment.

* fix(module): exempt Degraded condition from happy-condition aggregation

The shared odh-platform-utilities conditions.Manager treats any dependent
condition with Status=False (or Unknown) as "unhappy" and overwrites the
Ready condition's values with it, unless the dependent carries
Severity=Info. Degraded is an inverted-polarity condition (False means
healthy), so every healthy-case MarkFalse(Degraded, "FullyFunctional")
call was clobbering the correct MarkTrue(Ready, "AllServicesHealthy")
set moments earlier in the same reconcile. Mark all Degraded transitions
with WithSeverity(ConditionSeverityInfo) to exempt it from aggregation,
matching the existing precondition.WithSeverity(Info) pattern already
used for the optional KServe dependency check.

Found via live-cluster testing on tai-kpunwatk while replicating
Steven's opendatahub-operator PR #4051 test flow.

* fix(module): wire spec.enabledServices into --enable-services on the deployed operator

spec.EnabledServices was only ever read to decide which per-service
Deployments to health-check; nothing wrote it into the deployed
trustyai-service-operator workload's actual startup configuration. The
static manifests-template Deployment has no --enable-services arg or
ENABLED_SERVICES env var at all, so the underlying operator pod always
crash-looped with "please specify at least one service", regardless of
what the module CR requested.

Add injectEnabledServices, called in reconcileComponent after rendering
the Kustomize overlay and before SSA-applying it, which sets
--enable-services=<comma-separated names> on the manager container of
the trustyai-service-operator Deployment. When no services are
explicitly enabled (the current always-empty state, since
opendatahub-operator's BuildModuleCR does not yet project per-service
toggles onto this CR), default to enabling all five services, matching
the pre-modular in-tree operator's own default deployment args.

Found via live-cluster testing on tai-kpunwatk: with enabledServices
left at its zero value, updateHealthStatus ran zero health checkers and
trivially reported Ready=True while the core operator pod was
crash-looping the entire time.

* fix(module): create ClusterRole/ClusterRoleBinding for the deployed operator's RBAC

config/manifests-template only ever had a Deployment and ServiceAccount;
hack/get_trustyai_manifests.sh explicitly flags this as a placeholder
pending "download of the real operator manifests bundle". Because
migration.go's adoptRBACResources only adopts pre-existing, labeled
RoleBindings (never ClusterRole/ClusterRoleBinding, and never creates
anything from scratch), the deployed trustyai-service-operator workload
gets zero RBAC on any cluster that never had the old CSV/OLM-based
install to adopt from. Confirmed live: the pod crash-loops on
"apiservers.config.openshift.io ... is forbidden" before it can even
read its TLS profile.

Precedent: spark-operator-module (further along in this migration)
already creates its full RBAC surface fresh via SSA and declares
Owns(&rbacv1.ClusterRole{}) / Owns(&rbacv1.ClusterRoleBinding{}) in
SetupWithManager, because its CR is cluster-scoped. TrustyAI's CR is
namespace-scoped, so it cannot use owner-reference-based ownership for
cluster-scoped resources (Kubernetes rejects a namespace-scoped owner on
a cluster-scoped object) -- these are excluded from ownership via
deploy.WithExcludeFromOwnership and cleaned up explicitly in
handleDeletion instead.

Adds:
- config/manifests-template/base/rbac/: the tls-profile-reader
  ClusterRole (apiservers read, fixes the observed crash) plus each
  service's manager-role ClusterRole, ported from
  config/components/<service>/rbac/manager-rbac.yaml, each with a fresh
  ClusterRoleBinding to the trustyai-service-operator ServiceAccount
  (module's base has no namePrefix, so these intentionally use
  unprefixed names distinct from the classic CSV-installed ones).
- cluster_rbac.go: deleteClusterScopedRBAC, called from handleDeletion.
- Owns() for ServiceAccount/Service/Deployment/RoleBinding in
  SetupWithManager, matching spark-operator-module's pattern.
- RBAC markers + regenerated config/rbac/role.yaml for the module's own
  clusterroles/clusterrolebindings management.

* fix(module): grant the module its own workload RBAC to satisfy escalation checks

Kubernetes' RBAC escalation-prevention check rejects a create/update on a
ClusterRole/ClusterRoleBinding if the requesting principal doesn't already
hold every permission being granted. Confirmed live: the module's SA was
denied creating evalhub-manager-role with
"attempting to grant RBAC permissions not currently held".

Add workload_rbac.go, mirroring the exact rule sets from
config/manifests-template/base/rbac/*_clusterrole.yaml as kubebuilder
markers, so the module's own manager-role ClusterRole becomes a superset
of everything it grants to the deployed operator's ServiceAccount.

Also drops a dead "bind" rule on evalhub-manager-role that referenced
classic CSV-prefixed ClusterRole names (trustyai-service-operator-evalhub-*)
which this deployment path never creates.

* fix(module): provision webhook TLS certs for the deployed operator

trustyai-service-operator registers CRD conversion webhooks (EvalHub,
TrustyAIService) whenever TAS or EVALHUB is enabled, which requires TLS
certs at /tmp/k8s-webhook-server/serving-certs. config/manifests-template
had no webhook Service and no cert volume mount, so the deployed pod
crash-looped with "open .../tls.crt: no such file or directory" --
confirmed live, immediately after the RBAC fix got the pod past its
previous crash.

Ports config/components/webhook/webhook-service.yaml (a Service with the
OpenShift service-CA operator's serving-cert-secret-name annotation,
which auto-populates a webhook-server-cert Secret) and the volume/mount
from config/components/evalhub/patches/manager_webhook_config.yaml onto
the module's Deployment.

* fix(module): use default services for health checks

---------

Co-authored-by: Rui Vieira <ruidevieira@googlemail.com>
RobGeada pushed a commit to RobGeada/trustyai-service-operator that referenced this pull request Sep 18, 2026
Per Rui Vieira's review on trustyai-explainability#913: add deny rules to policy/clusterrole.rego
that fail if the trustyai-operator-module-manager-role ClusterRole ever
loses the watch verb on services or prometheuses, plus unit tests
covering both the violation and the passing case. Also adds
monitoring.coreos.com/prometheuses to the Layer 1 allowlist, which was
missing despite the module already having get/list on it.

Note: trustyai-operator-module isn't yet one of the kustomize entry
points make policy-check renders (only the in-tree config/overlays/*
paths are), so these rules currently only run via the unit tests in
clusterrole_test.rego, not against the module's real rendered manifests.
Wiring the module into the live policy-check workflow is a separate,
larger change.
RobGeada pushed a commit to RobGeada/trustyai-service-operator that referenced this pull request Sep 18, 2026
…y condition aggregation (trustyai-explainability#915)

* fix(module): add missing RBAC for serviceaccounts

Same root cause as trustyai-explainability#908/trustyai-explainability#913: the module's generic deploy pipeline
looks up the pre-existing in-tree trustyai-service-operator
ServiceAccount before applying the workload manifests, but the
ClusterRole never granted any permission on serviceaccounts at all.

Found via a live cluster test: after fixing the /opt/manifests
permission issue and confirming it resolved, the reconciler
progressed further and hit this as the next blocker:

  serviceaccounts "trustyai-service-operator" is forbidden: User
  "system:serviceaccount:opendatahub:trustyai-operator-module-controller-manager"
  cannot get resource "serviceaccounts" in API group "" in the
  namespace "opendatahub"

* fix(module): add create verb for services, serviceaccounts, deployments, rolebindings

Found via the same live test: after fixing the missing serviceaccounts
RBAC entirely, the next error was "cannot create resource
serviceaccounts" rather than "cannot get". Kubernetes' Server-Side
Apply requires the create verb (not just patch) when the target
object does not already exist yet -- true for these four resource
types on a fresh deploy (new namespace, nothing pre-adopted from an
in-tree controller). patch alone was only sufficient in scenarios
where the object already existed (e.g. adopting from a prior in-tree
install), which is why this wasn't caught until testing a genuinely
fresh deployment.

* fix(module): exempt Degraded condition from happy-condition aggregation

The shared odh-platform-utilities conditions.Manager treats any dependent
condition with Status=False (or Unknown) as "unhappy" and overwrites the
Ready condition's values with it, unless the dependent carries
Severity=Info. Degraded is an inverted-polarity condition (False means
healthy), so every healthy-case MarkFalse(Degraded, "FullyFunctional")
call was clobbering the correct MarkTrue(Ready, "AllServicesHealthy")
set moments earlier in the same reconcile. Mark all Degraded transitions
with WithSeverity(ConditionSeverityInfo) to exempt it from aggregation,
matching the existing precondition.WithSeverity(Info) pattern already
used for the optional KServe dependency check.

Found via live-cluster testing on tai-kpunwatk while replicating
Steven's opendatahub-operator PR #4051 test flow.

* fix(module): wire spec.enabledServices into --enable-services on the deployed operator

spec.EnabledServices was only ever read to decide which per-service
Deployments to health-check; nothing wrote it into the deployed
trustyai-service-operator workload's actual startup configuration. The
static manifests-template Deployment has no --enable-services arg or
ENABLED_SERVICES env var at all, so the underlying operator pod always
crash-looped with "please specify at least one service", regardless of
what the module CR requested.

Add injectEnabledServices, called in reconcileComponent after rendering
the Kustomize overlay and before SSA-applying it, which sets
--enable-services=<comma-separated names> on the manager container of
the trustyai-service-operator Deployment. When no services are
explicitly enabled (the current always-empty state, since
opendatahub-operator's BuildModuleCR does not yet project per-service
toggles onto this CR), default to enabling all five services, matching
the pre-modular in-tree operator's own default deployment args.

Found via live-cluster testing on tai-kpunwatk: with enabledServices
left at its zero value, updateHealthStatus ran zero health checkers and
trivially reported Ready=True while the core operator pod was
crash-looping the entire time.
RobGeada pushed a commit to RobGeada/trustyai-service-operator that referenced this pull request Sep 18, 2026
…operator on fresh installs (trustyai-explainability#918)

* fix(module): add missing RBAC for serviceaccounts

Same root cause as trustyai-explainability#908/trustyai-explainability#913: the module's generic deploy pipeline
looks up the pre-existing in-tree trustyai-service-operator
ServiceAccount before applying the workload manifests, but the
ClusterRole never granted any permission on serviceaccounts at all.

Found via a live cluster test: after fixing the /opt/manifests
permission issue and confirming it resolved, the reconciler
progressed further and hit this as the next blocker:

  serviceaccounts "trustyai-service-operator" is forbidden: User
  "system:serviceaccount:opendatahub:trustyai-operator-module-controller-manager"
  cannot get resource "serviceaccounts" in API group "" in the
  namespace "opendatahub"

* fix(module): add create verb for services, serviceaccounts, deployments, rolebindings

Found via the same live test: after fixing the missing serviceaccounts
RBAC entirely, the next error was "cannot create resource
serviceaccounts" rather than "cannot get". Kubernetes' Server-Side
Apply requires the create verb (not just patch) when the target
object does not already exist yet -- true for these four resource
types on a fresh deploy (new namespace, nothing pre-adopted from an
in-tree controller). patch alone was only sufficient in scenarios
where the object already existed (e.g. adopting from a prior in-tree
install), which is why this wasn't caught until testing a genuinely
fresh deployment.

* fix(module): exempt Degraded condition from happy-condition aggregation

The shared odh-platform-utilities conditions.Manager treats any dependent
condition with Status=False (or Unknown) as "unhappy" and overwrites the
Ready condition's values with it, unless the dependent carries
Severity=Info. Degraded is an inverted-polarity condition (False means
healthy), so every healthy-case MarkFalse(Degraded, "FullyFunctional")
call was clobbering the correct MarkTrue(Ready, "AllServicesHealthy")
set moments earlier in the same reconcile. Mark all Degraded transitions
with WithSeverity(ConditionSeverityInfo) to exempt it from aggregation,
matching the existing precondition.WithSeverity(Info) pattern already
used for the optional KServe dependency check.

Found via live-cluster testing on tai-kpunwatk while replicating
Steven's opendatahub-operator PR #4051 test flow.

* fix(module): wire spec.enabledServices into --enable-services on the deployed operator

spec.EnabledServices was only ever read to decide which per-service
Deployments to health-check; nothing wrote it into the deployed
trustyai-service-operator workload's actual startup configuration. The
static manifests-template Deployment has no --enable-services arg or
ENABLED_SERVICES env var at all, so the underlying operator pod always
crash-looped with "please specify at least one service", regardless of
what the module CR requested.

Add injectEnabledServices, called in reconcileComponent after rendering
the Kustomize overlay and before SSA-applying it, which sets
--enable-services=<comma-separated names> on the manager container of
the trustyai-service-operator Deployment. When no services are
explicitly enabled (the current always-empty state, since
opendatahub-operator's BuildModuleCR does not yet project per-service
toggles onto this CR), default to enabling all five services, matching
the pre-modular in-tree operator's own default deployment args.

Found via live-cluster testing on tai-kpunwatk: with enabledServices
left at its zero value, updateHealthStatus ran zero health checkers and
trivially reported Ready=True while the core operator pod was
crash-looping the entire time.

* fix(module): create ClusterRole/ClusterRoleBinding for the deployed operator's RBAC

config/manifests-template only ever had a Deployment and ServiceAccount;
hack/get_trustyai_manifests.sh explicitly flags this as a placeholder
pending "download of the real operator manifests bundle". Because
migration.go's adoptRBACResources only adopts pre-existing, labeled
RoleBindings (never ClusterRole/ClusterRoleBinding, and never creates
anything from scratch), the deployed trustyai-service-operator workload
gets zero RBAC on any cluster that never had the old CSV/OLM-based
install to adopt from. Confirmed live: the pod crash-loops on
"apiservers.config.openshift.io ... is forbidden" before it can even
read its TLS profile.

Precedent: spark-operator-module (further along in this migration)
already creates its full RBAC surface fresh via SSA and declares
Owns(&rbacv1.ClusterRole{}) / Owns(&rbacv1.ClusterRoleBinding{}) in
SetupWithManager, because its CR is cluster-scoped. TrustyAI's CR is
namespace-scoped, so it cannot use owner-reference-based ownership for
cluster-scoped resources (Kubernetes rejects a namespace-scoped owner on
a cluster-scoped object) -- these are excluded from ownership via
deploy.WithExcludeFromOwnership and cleaned up explicitly in
handleDeletion instead.

Adds:
- config/manifests-template/base/rbac/: the tls-profile-reader
  ClusterRole (apiservers read, fixes the observed crash) plus each
  service's manager-role ClusterRole, ported from
  config/components/<service>/rbac/manager-rbac.yaml, each with a fresh
  ClusterRoleBinding to the trustyai-service-operator ServiceAccount
  (module's base has no namePrefix, so these intentionally use
  unprefixed names distinct from the classic CSV-installed ones).
- cluster_rbac.go: deleteClusterScopedRBAC, called from handleDeletion.
- Owns() for ServiceAccount/Service/Deployment/RoleBinding in
  SetupWithManager, matching spark-operator-module's pattern.
- RBAC markers + regenerated config/rbac/role.yaml for the module's own
  clusterroles/clusterrolebindings management.

* fix(module): grant the module its own workload RBAC to satisfy escalation checks

Kubernetes' RBAC escalation-prevention check rejects a create/update on a
ClusterRole/ClusterRoleBinding if the requesting principal doesn't already
hold every permission being granted. Confirmed live: the module's SA was
denied creating evalhub-manager-role with
"attempting to grant RBAC permissions not currently held".

Add workload_rbac.go, mirroring the exact rule sets from
config/manifests-template/base/rbac/*_clusterrole.yaml as kubebuilder
markers, so the module's own manager-role ClusterRole becomes a superset
of everything it grants to the deployed operator's ServiceAccount.

Also drops a dead "bind" rule on evalhub-manager-role that referenced
classic CSV-prefixed ClusterRole names (trustyai-service-operator-evalhub-*)
which this deployment path never creates.

* fix(module): provision webhook TLS certs for the deployed operator

trustyai-service-operator registers CRD conversion webhooks (EvalHub,
TrustyAIService) whenever TAS or EVALHUB is enabled, which requires TLS
certs at /tmp/k8s-webhook-server/serving-certs. config/manifests-template
had no webhook Service and no cert volume mount, so the deployed pod
crash-looped with "open .../tls.crt: no such file or directory" --
confirmed live, immediately after the RBAC fix got the pod past its
previous crash.

Ports config/components/webhook/webhook-service.yaml (a Service with the
OpenShift service-CA operator's serving-cert-secret-name annotation,
which auto-populates a webhook-server-cert Secret) and the volume/mount
from config/components/evalhub/patches/manager_webhook_config.yaml onto
the module's Deployment.

* fix(module): use default services for health checks

---------

Co-authored-by: Rui Vieira <ruidevieira@googlemail.com>
openshift-merge-bot Bot pushed a commit to opendatahub-io/opendatahub-operator that referenced this pull request Sep 22, 2026
* feat: migrate TrustyAI from component to module

Rewrites the TrustyAI module handler against the current ModuleHandler
interface (PopulatePlatformModule/IsEnabled/BuildModuleCR signatures and
ModuleConfig fields changed significantly since the previous attempt in
PR #3643, which predates the DAG-orchestration and common
action-framework refactors). Follows sparkoperator's handler as the
template, since both are out-of-tree kustomize-based modules with their
own dedicated operator image.

- Add internal/controller/modules/trustyai/handler.go + handler_test.go
- Add PlatformModules.TrustyAI field (DSC mode only for now; Platform/xKS
  mode deferred pending API updates, matching the original migration's
  scope decision)
- Register TrustyAI in existingModules/moduleRunlevels (RL(32), after
  KServe's RL(31), since TrustyAI depends on KServe CRDs)
- Remove TrustyAI from existingComponents/componentRunlevels and delete
  the legacy in-tree component controller and its monitoring resources
- CRName uses componentApi.TrustyAIInstanceName ("default-trustyai"),
  now aligned with the trustyai-operator-module CRD's own singleton
  validation (see companion fix in trustyai-service-operator)

* fix(trustyai): use current image var name, add RHOAI build-config exception

RELATED_IMAGE_ODH_TRUSTYAI_SERVICE_IMAGE was renamed to
RELATED_IMAGE_ODH_TRUSTYAI_SERVICE_PY_IMAGE (#4011) before this
migration branch was rebased; the RelatedImages list still had the
stale name. Also adds an RHOAI-Build-Config exception for the new
RELATED_IMAGE_ODH_TRUSTYAI_MODULE_OPERATOR_IMAGE, matching the
trainer/sparkoperator module operator images (RHOAIENG-80231,
delivering in RHOAI 3.6 EA2). Confirmed via `make validate-related-images`
locally: 2 errors -> 0.

* docs: regenerate api-overview for TrustyAI module field

make generate manifests-all api-docs picked up the new
PlatformModules.TrustyAI field added in the previous commit.

* fix(trustyai): register manifests-config entry, fix ManifestDir/ContextDir mismatch

Two bugs causing "must build at directory: not a valid directory:
evalsymlink failure on '/opt/manifests/trustyai-module-operator/default'"
at deploy time:

- manifests-config.yaml had no entry for the module at all, so
  `make get-manifests` never fetched it. Added trustyai-module-operator,
  reusing the exact same repo/ref pins as the existing trustyai
  (component) entry, since both mirrors already have
  trustyai-operator-module/ at those SHAs -- confirmed via the GitHub
  API before adding.

- handler.go's ManifestDir was missing ContextDir: "default". Every
  other kustomize-based module (e.g. trainer) points sourcePath at the
  whole `config` directory and lets ContextDir select the `default`
  overlay at render time; PlatformModules.String() joins Path+ContextDir,
  so without it the platform looked for manifests directly under
  ManifestDir instead of ManifestDir/default -- the exact directory
  missing in the reported error.

Verified end to end: `make get-manifests` now fetches
trustyai-operator-module/config/{crd,rbac,manager,default} correctly,
and `kustomize build opt/manifests/trustyai-module-operator/default`
renders successfully.

* fix(trustyai): align RBAC markers and e2e tests with module architecture

RBAC markers for the trustyais CR belonged in internal/controller/modules
(module-managed), not datasciencecluster; moved them accordingly with no
change to generated output. Updated dag_ordering_test.go and
resilience_test.go to treat TrustyAI as a module like the other
already-migrated components (Trainer, Workbenches, Dashboard, AIHub),
matching gaps found by comparing against PR #4000.

* fix(trustyai): reuse existing trustyai manifests-config entry instead of a duplicate

The old trustyai entry (sourcePath: config) pointed at the removed in-tree
component manifests and had no remaining code consumer, so keeping a
separate trustyai-module-operator entry alongside it was redundant.
Repointed the existing entry's sourcePath at the module manifests and
dropped the duplicate.

* fix(trustyai): explicitly map permit fields instead of blind struct conversion

BuildModuleCR converted the whole DSC-facing TrustyAICommonSpec into the
module CR's spec via runtime.DefaultUnstructuredConverter. The DSC-facing
API declares permitCodeExecution/permitOnline as a string enum
("allow"/"deny") while the module CRD declares them as bool, so the
wholesale conversion projected a string into a field the module's typed
client expects to be bool, breaking its watch/list reflector on
existing CRs. Map the two permit fields explicitly with the
string-to-bool conversion instead.

Reported-by: Steven Tobin

* fix(trustyai): bump rhoai manifest pin to pick up CR-naming fix

The rhoai-3.6-ea.2 pin predated commit 5ed6709 (fix(module): align
TrustyAI singleton CR name with sibling modules), so the deployed
module CRD still required metadata.name == 'default' while
BuildModuleCR creates 'default-trustyai', causing every apply to be
rejected by the CRD's own CEL validation. Bumped to the branch's
current tip, which already includes the fix (verified via
GitHub compare API and by re-downloading manifests locally).

The odh pin (opendatahub-io/trustyai-service-operator@incubation)
still needs the same fix; that branch hasn't been re-synced from
main since before the fix landed, so it's left unchanged pending
that sync.

Reported-by: Steven Tobin

* fix(trustyai): switch odh manifest pin from incubation to main

The incubation branch (opendatahub-io/trustyai-service-operator) is
synced from that repo's own main via a separate, infrequent process
(last sync 2026-08-05), and hasn't picked up the CR-naming CEL fix
(commit 5ed6709, landed 2026-09-01) that the rhoai pin already has.
Waiting on that sync is unbounded, and this file already has
precedent for pinning odh directly at main (see the modelregistry
entry), so switch to main@<sha> instead. Verified the pinned commit
contains the fix by fetching the CRD directly and confirming the
CEL rule reads 'default-trustyai', and by re-running
`make get-manifests` + `kustomize build` locally.

Reported-by: Steven Tobin

* fix(trustyai): bump odh/rhoai manifest pins to pick up module operator fixes

trustyai-service-operator#908 (RBAC watch verbs, /opt write permission,
platform version handshake) has now propagated through both downstream
sync chains:

- odh: opendatahub-io/trustyai-service-operator#194 merged
- rhoai: red-hat-data-services/trustyai-service-operator#1932 (into
  main) and #1934 (main into rhoai-3.6-ea.2) both merged

Bumped both pins to the post-merge tips and verified locally: the
"watch" verb is present on deployments/rolebindings in both ODH and
RHOAI mode, kustomize build succeeds, and go build/test pass.

Reported-by: Steven Tobin

* fix(trustyai): remove redundant CRD-generating types now owned by the module

TrustyAI's Go type still had +kubebuilder:object:root=true on TrustyAI/
TrustyAIList, plus the PlatformObject interface implementation and
scheme registration. Since the CRD is now installed by
trustyai-operator-module (from its own manifests), this caused
opendatahub-operator to generate a second, competing CRD definition
for the same GVK from a Go type that isn't kept in sync with the
module's actual schema (already missing mcpGuardrailsMode and the
default-trustyai singleton validation independently added on each
side).

Removed the top-level TrustyAI/TrustyAIList struct, scheme
registration, and PlatformObject methods, keeping only the nested
spec/status types and DSCTrustyAI/DSCTrustyAIStatus, matching the
pattern already used by sibling modules (see sparkoperator_types.go).

Updated tests/e2e/trustyai_test.go to use NewModuleTestCtx (GVK +
instance name) instead of NewComponentTestCtx (which required a typed
struct) -- this is the existing mechanism other module e2e tests
(kserve_test.go, aigateway_test.go) already use for exactly this case.

Verified via a full config/crd/bases wipe + make manifests that no
components.platform.opendatahub.io_trustyais.yaml is generated anymore.

* fix(trustyai): bump rhoai manifest pin to pick up #913 (services/prometheuses watch)

trustyai-explainability/trustyai-service-operator#913 merged and has
propagated through both red-hat-data-services sync hops (main, then
rhoai-3.6-ea.2). Bumped the rhoai ref and verified locally: services
and prometheuses RBAC rules now include watch.

The odh pin is not bumped yet -- opendatahub-io/trustyai-service-operator#195
(the pull[bot] sync PR) already has the right content but hasn't merged.

* fix(trustyai): temporarily point odh pin at canonical upstream repo

opendatahub-io/trustyai-service-operator#195 (the pull[bot] sync PR
bringing in #913's services/prometheuses watch fix) already has the
right content as its head commit, but is stuck on the OWNERS approval
gate while the approver is on PTO. Rather than wait, point the odh
pin directly at trustyai-explainability/trustyai-service-operator
(the canonical upstream #195 pulls from) at that same commit.

Verified locally: make get-manifests resolves cleanly, RBAC role.yaml
has watch on services/prometheuses, CRD CEL rule reads
'default-trustyai', kustomize build succeeds, go build/test pass.

Switch back to the opendatahub-io mirror once #195 merges.

* switch ODH manifest pin back to opendatahub-io mirror

* bump(trustyai): rhoai manifest pin to pick up rhoai PR #1943

Picks up the same fixes that were merged into the ODH upstream repo.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* bump(trustyai): manifest pins for #920/#921/#925 sync

Pick up post-sync mirror SHAs: ODH main @0b428d2 (#197) and
rhoai-3.6-ea.2 @5f278d7 (#1949), including the emptyDir/clearDir fix.

Co-authored-by: Cursor <cursoragent@cursor.com>

* bump(trustyai): update module operator image digest to latest build

* sync manager image overrides and regenerate api docs

* chore: resolve image digests after trustyai manifest sync

* bump(trustyai): service operator image only; revert broad digest resolve
Update RELATED_IMAGE_ODH_TRUSTYAI_SERVICE_OPERATOR_IMAGE to fix
--metrics-cert-path crash. Reverts platform-wide resolve-image-digests
from 4e0700d; module operator stays at e5cd204a (#925).

* fix(trustyai): TrustyAI-only image pins at latest validated digests

* fix(trustyai): drop unrelated data-registry rhoai digest from PR

Keep manifests-config image pin changes TrustyAI-only; remove
MOD_ARCH_DATA_REGISTRY rhoai block not in scope for #4051.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Revert "fix(trustyai): drop unrelated data-registry rhoai digest from PR"
Restore MOD_ARCH_DATA_REGISTRY rhoai mapping from upstream #4073 merge.

* test(e2e): drop TrustyAI from platform-owned CRD validation

TrustyAI is now an out-of-tree module; its CRD is installed by the
module operator when enabled, not by the platform operator at startup.

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(manifests): bump trustyai rhoai ref after #1955 sync

Pick up #926 module operator fixes on rhoai-3.6-ea.2 (RELATED_IMAGE mapping,
manifest staging, memory limits).

* chore(manifests): bump trustyai odh ref to pick up #926 fixes

Sync opendatahub-io/trustyai-service-operator main to #202 merge (9c0ffa8).

* chore(manifests): bump trustyai pins for #928 readiness fix

- ODH main@feb5b94 (opendatahub-io #204)
- RHOAI rhoai-3.6-ea.2@75a4af5 (#1956 / #928 on rhoai)
- Module-operator image digest d2df18f5 (Konflux build for feb5b94)

* chore(manifests): align TrustyAI module-operator image with build configs

- ODH: quay.io/opendatahub/odh-trustyai-module-operator@b3cef1149
- RHOAI: quay.io/rhoai/odh-trustyai-module-operator-rhel9@51384d9610
- Remove stale rhoai_exceptions entry (RHOAIENG-80231 onboarded)

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat(trustyai): propagate MCP Guardrails mode

Signed-off-by: Rui Vieira <ruidevieira@googlemail.com>
(cherry picked from commit 8a76d74)

* chore(docs): regenerate api-overview after Ray/TrustyAI module migration

Drop Ray and TrustyAI from the platform-owned Resource Types index so
make api-docs matches CI (Ensure generated files are included).

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(trustyai): bump module refs to include mcpGuardrailsMode in CRD schema

ODH: main@7838ba58fffca619610c6b260c4ec87c031d0a27 (was feb5b944...)
RHOAI: rhoai-3.6-ea.2@9a3b7b9f4f936419451c833b70ba180fe03a3e61 (was 75a4af5b...)

The previous refs lacked mcpGuardrailsMode in the TrustyAI CRD schema,
causing deployment failures when trying to apply the field: "field not
declared in schema". Newer upstream commits include this field.

Fixes e2e test failure: "unable to patch components.platform.opendatahub.io/v1alpha1,
Kind=TrustyAI default-trustyai: failed to create typed patch object: .spec.mcpGuardrailsMode:
field not declared in schema"

* fix(trustyai): bump module operator image digests to latest builds

Update both ODH and RHOAI image digests to the latest trustyai-module-operator
builds that include the MCP guardrails fix:

- ODH: sha256:65cba5211000f9b13b87b2bd9f1fb052ea814e1826e53f498edd84209d877b6e
  (midstream incubation→stable sync by Rui, synced to ODH)
- RHOAI: sha256:e6a0be61ecbedf259b80fc2a63ae8ee975aa18a6a6743538554b21bab9f7bd4c

Previous digests were stale and not available in the registry, causing
e2e test deployment failures with missing CRD fields.

* test(trustyai): override ValidateOperandsOwnerReferences for modular architecture

TrustyAI is now a module operator that deploys operands with different labels.
Override the parent test implementation to use the correct TrustyAI-specific label
'app.kubernetes.io/part-of: trustyai' instead of the generic component label,
and verify operands are owned by the TrustyAI CR by kind and name.

* style(test): fix import order in trustyai_test.go

* align the trusty tests to the module architecture

* Align RHOAI TrustyAI Module operator with RHOAI manifests

* Fix Missing Components CRD Handling test

---------

Signed-off-by: Rui Vieira <ruidevieira@googlemail.com>
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Rui Vieira <ruidevieira@googlemail.com>
Co-authored-by: Steven Tobin <stobin@redhat.com>
Co-authored-by: Marián Macik <mmacik@redhat.com>
mchamsed-rh pushed a commit to red-hat-data-services/rhods-operator that referenced this pull request Sep 22, 2026
* feat: migrate TrustyAI from component to module

Rewrites the TrustyAI module handler against the current ModuleHandler
interface (PopulatePlatformModule/IsEnabled/BuildModuleCR signatures and
ModuleConfig fields changed significantly since the previous attempt in
PR #3643, which predates the DAG-orchestration and common
action-framework refactors). Follows sparkoperator's handler as the
template, since both are out-of-tree kustomize-based modules with their
own dedicated operator image.

- Add internal/controller/modules/trustyai/handler.go + handler_test.go
- Add PlatformModules.TrustyAI field (DSC mode only for now; Platform/xKS
  mode deferred pending API updates, matching the original migration's
  scope decision)
- Register TrustyAI in existingModules/moduleRunlevels (RL(32), after
  KServe's RL(31), since TrustyAI depends on KServe CRDs)
- Remove TrustyAI from existingComponents/componentRunlevels and delete
  the legacy in-tree component controller and its monitoring resources
- CRName uses componentApi.TrustyAIInstanceName ("default-trustyai"),
  now aligned with the trustyai-operator-module CRD's own singleton
  validation (see companion fix in trustyai-service-operator)

* fix(trustyai): use current image var name, add RHOAI build-config exception

RELATED_IMAGE_ODH_TRUSTYAI_SERVICE_IMAGE was renamed to
RELATED_IMAGE_ODH_TRUSTYAI_SERVICE_PY_IMAGE (#4011) before this
migration branch was rebased; the RelatedImages list still had the
stale name. Also adds an RHOAI-Build-Config exception for the new
RELATED_IMAGE_ODH_TRUSTYAI_MODULE_OPERATOR_IMAGE, matching the
trainer/sparkoperator module operator images (RHOAIENG-80231,
delivering in RHOAI 3.6 EA2). Confirmed via `make validate-related-images`
locally: 2 errors -> 0.

* docs: regenerate api-overview for TrustyAI module field

make generate manifests-all api-docs picked up the new
PlatformModules.TrustyAI field added in the previous commit.

* fix(trustyai): register manifests-config entry, fix ManifestDir/ContextDir mismatch

Two bugs causing "must build at directory: not a valid directory:
evalsymlink failure on '/opt/manifests/trustyai-module-operator/default'"
at deploy time:

- manifests-config.yaml had no entry for the module at all, so
  `make get-manifests` never fetched it. Added trustyai-module-operator,
  reusing the exact same repo/ref pins as the existing trustyai
  (component) entry, since both mirrors already have
  trustyai-operator-module/ at those SHAs -- confirmed via the GitHub
  API before adding.

- handler.go's ManifestDir was missing ContextDir: "default". Every
  other kustomize-based module (e.g. trainer) points sourcePath at the
  whole `config` directory and lets ContextDir select the `default`
  overlay at render time; PlatformModules.String() joins Path+ContextDir,
  so without it the platform looked for manifests directly under
  ManifestDir instead of ManifestDir/default -- the exact directory
  missing in the reported error.

Verified end to end: `make get-manifests` now fetches
trustyai-operator-module/config/{crd,rbac,manager,default} correctly,
and `kustomize build opt/manifests/trustyai-module-operator/default`
renders successfully.

* fix(trustyai): align RBAC markers and e2e tests with module architecture

RBAC markers for the trustyais CR belonged in internal/controller/modules
(module-managed), not datasciencecluster; moved them accordingly with no
change to generated output. Updated dag_ordering_test.go and
resilience_test.go to treat TrustyAI as a module like the other
already-migrated components (Trainer, Workbenches, Dashboard, AIHub),
matching gaps found by comparing against PR #4000.

* fix(trustyai): reuse existing trustyai manifests-config entry instead of a duplicate

The old trustyai entry (sourcePath: config) pointed at the removed in-tree
component manifests and had no remaining code consumer, so keeping a
separate trustyai-module-operator entry alongside it was redundant.
Repointed the existing entry's sourcePath at the module manifests and
dropped the duplicate.

* fix(trustyai): explicitly map permit fields instead of blind struct conversion

BuildModuleCR converted the whole DSC-facing TrustyAICommonSpec into the
module CR's spec via runtime.DefaultUnstructuredConverter. The DSC-facing
API declares permitCodeExecution/permitOnline as a string enum
("allow"/"deny") while the module CRD declares them as bool, so the
wholesale conversion projected a string into a field the module's typed
client expects to be bool, breaking its watch/list reflector on
existing CRs. Map the two permit fields explicitly with the
string-to-bool conversion instead.

Reported-by: Steven Tobin

* fix(trustyai): bump rhoai manifest pin to pick up CR-naming fix

The rhoai-3.6-ea.2 pin predated commit 5ed6709 (fix(module): align
TrustyAI singleton CR name with sibling modules), so the deployed
module CRD still required metadata.name == 'default' while
BuildModuleCR creates 'default-trustyai', causing every apply to be
rejected by the CRD's own CEL validation. Bumped to the branch's
current tip, which already includes the fix (verified via
GitHub compare API and by re-downloading manifests locally).

The odh pin (opendatahub-io/trustyai-service-operator@incubation)
still needs the same fix; that branch hasn't been re-synced from
main since before the fix landed, so it's left unchanged pending
that sync.

Reported-by: Steven Tobin

* fix(trustyai): switch odh manifest pin from incubation to main

The incubation branch (opendatahub-io/trustyai-service-operator) is
synced from that repo's own main via a separate, infrequent process
(last sync 2026-08-05), and hasn't picked up the CR-naming CEL fix
(commit 5ed6709, landed 2026-09-01) that the rhoai pin already has.
Waiting on that sync is unbounded, and this file already has
precedent for pinning odh directly at main (see the modelregistry
entry), so switch to main@<sha> instead. Verified the pinned commit
contains the fix by fetching the CRD directly and confirming the
CEL rule reads 'default-trustyai', and by re-running
`make get-manifests` + `kustomize build` locally.

Reported-by: Steven Tobin

* fix(trustyai): bump odh/rhoai manifest pins to pick up module operator fixes

trustyai-service-operator#908 (RBAC watch verbs, /opt write permission,
platform version handshake) has now propagated through both downstream
sync chains:

- odh: opendatahub-io/trustyai-service-operator#194 merged
- rhoai: red-hat-data-services/trustyai-service-operator#1932 (into
  main) and #1934 (main into rhoai-3.6-ea.2) both merged

Bumped both pins to the post-merge tips and verified locally: the
"watch" verb is present on deployments/rolebindings in both ODH and
RHOAI mode, kustomize build succeeds, and go build/test pass.

Reported-by: Steven Tobin

* fix(trustyai): remove redundant CRD-generating types now owned by the module

TrustyAI's Go type still had +kubebuilder:object:root=true on TrustyAI/
TrustyAIList, plus the PlatformObject interface implementation and
scheme registration. Since the CRD is now installed by
trustyai-operator-module (from its own manifests), this caused
opendatahub-operator to generate a second, competing CRD definition
for the same GVK from a Go type that isn't kept in sync with the
module's actual schema (already missing mcpGuardrailsMode and the
default-trustyai singleton validation independently added on each
side).

Removed the top-level TrustyAI/TrustyAIList struct, scheme
registration, and PlatformObject methods, keeping only the nested
spec/status types and DSCTrustyAI/DSCTrustyAIStatus, matching the
pattern already used by sibling modules (see sparkoperator_types.go).

Updated tests/e2e/trustyai_test.go to use NewModuleTestCtx (GVK +
instance name) instead of NewComponentTestCtx (which required a typed
struct) -- this is the existing mechanism other module e2e tests
(kserve_test.go, aigateway_test.go) already use for exactly this case.

Verified via a full config/crd/bases wipe + make manifests that no
components.platform.opendatahub.io_trustyais.yaml is generated anymore.

* fix(trustyai): bump rhoai manifest pin to pick up #913 (services/prometheuses watch)

trustyai-explainability/trustyai-service-operator#913 merged and has
propagated through both red-hat-data-services sync hops (main, then
rhoai-3.6-ea.2). Bumped the rhoai ref and verified locally: services
and prometheuses RBAC rules now include watch.

The odh pin is not bumped yet -- opendatahub-io/trustyai-service-operator#195
(the pull[bot] sync PR) already has the right content but hasn't merged.

* fix(trustyai): temporarily point odh pin at canonical upstream repo

opendatahub-io/trustyai-service-operator#195 (the pull[bot] sync PR
bringing in #913's services/prometheuses watch fix) already has the
right content as its head commit, but is stuck on the OWNERS approval
gate while the approver is on PTO. Rather than wait, point the odh
pin directly at trustyai-explainability/trustyai-service-operator
(the canonical upstream #195 pulls from) at that same commit.

Verified locally: make get-manifests resolves cleanly, RBAC role.yaml
has watch on services/prometheuses, CRD CEL rule reads
'default-trustyai', kustomize build succeeds, go build/test pass.

Switch back to the opendatahub-io mirror once #195 merges.

* switch ODH manifest pin back to opendatahub-io mirror

* bump(trustyai): rhoai manifest pin to pick up rhoai PR #1943

Picks up the same fixes that were merged into the ODH upstream repo.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* bump(trustyai): manifest pins for #920/#921/#925 sync

Pick up post-sync mirror SHAs: ODH main @0b428d2 (#197) and
rhoai-3.6-ea.2 @5f278d7 (#1949), including the emptyDir/clearDir fix.

Co-authored-by: Cursor <cursoragent@cursor.com>

* bump(trustyai): update module operator image digest to latest build

* sync manager image overrides and regenerate api docs

* chore: resolve image digests after trustyai manifest sync

* bump(trustyai): service operator image only; revert broad digest resolve
Update RELATED_IMAGE_ODH_TRUSTYAI_SERVICE_OPERATOR_IMAGE to fix
--metrics-cert-path crash. Reverts platform-wide resolve-image-digests
from 4e0700d; module operator stays at e5cd204a (#925).

* fix(trustyai): TrustyAI-only image pins at latest validated digests

* fix(trustyai): drop unrelated data-registry rhoai digest from PR

Keep manifests-config image pin changes TrustyAI-only; remove
MOD_ARCH_DATA_REGISTRY rhoai block not in scope for #4051.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Revert "fix(trustyai): drop unrelated data-registry rhoai digest from PR"
Restore MOD_ARCH_DATA_REGISTRY rhoai mapping from upstream #4073 merge.

* test(e2e): drop TrustyAI from platform-owned CRD validation

TrustyAI is now an out-of-tree module; its CRD is installed by the
module operator when enabled, not by the platform operator at startup.

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(manifests): bump trustyai rhoai ref after #1955 sync

Pick up #926 module operator fixes on rhoai-3.6-ea.2 (RELATED_IMAGE mapping,
manifest staging, memory limits).

* chore(manifests): bump trustyai odh ref to pick up #926 fixes

Sync opendatahub-io/trustyai-service-operator main to #202 merge (9c0ffa8).

* chore(manifests): bump trustyai pins for #928 readiness fix

- ODH main@feb5b94 (opendatahub-io #204)
- RHOAI rhoai-3.6-ea.2@75a4af5 (#1956 / #928 on rhoai)
- Module-operator image digest d2df18f5 (Konflux build for feb5b94)

* chore(manifests): align TrustyAI module-operator image with build configs

- ODH: quay.io/opendatahub/odh-trustyai-module-operator@b3cef1149
- RHOAI: quay.io/rhoai/odh-trustyai-module-operator-rhel9@51384d9610
- Remove stale rhoai_exceptions entry (RHOAIENG-80231 onboarded)

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat(trustyai): propagate MCP Guardrails mode

Signed-off-by: Rui Vieira <ruidevieira@googlemail.com>
(cherry picked from commit 8a76d74)

* chore(docs): regenerate api-overview after Ray/TrustyAI module migration

Drop Ray and TrustyAI from the platform-owned Resource Types index so
make api-docs matches CI (Ensure generated files are included).

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(trustyai): bump module refs to include mcpGuardrailsMode in CRD schema

ODH: main@7838ba58fffca619610c6b260c4ec87c031d0a27 (was feb5b944...)
RHOAI: rhoai-3.6-ea.2@9a3b7b9f4f936419451c833b70ba180fe03a3e61 (was 75a4af5b...)

The previous refs lacked mcpGuardrailsMode in the TrustyAI CRD schema,
causing deployment failures when trying to apply the field: "field not
declared in schema". Newer upstream commits include this field.

Fixes e2e test failure: "unable to patch components.platform.opendatahub.io/v1alpha1,
Kind=TrustyAI default-trustyai: failed to create typed patch object: .spec.mcpGuardrailsMode:
field not declared in schema"

* fix(trustyai): bump module operator image digests to latest builds

Update both ODH and RHOAI image digests to the latest trustyai-module-operator
builds that include the MCP guardrails fix:

- ODH: sha256:65cba5211000f9b13b87b2bd9f1fb052ea814e1826e53f498edd84209d877b6e
  (midstream incubation→stable sync by Rui, synced to ODH)
- RHOAI: sha256:e6a0be61ecbedf259b80fc2a63ae8ee975aa18a6a6743538554b21bab9f7bd4c

Previous digests were stale and not available in the registry, causing
e2e test deployment failures with missing CRD fields.

* test(trustyai): override ValidateOperandsOwnerReferences for modular architecture

TrustyAI is now a module operator that deploys operands with different labels.
Override the parent test implementation to use the correct TrustyAI-specific label
'app.kubernetes.io/part-of: trustyai' instead of the generic component label,
and verify operands are owned by the TrustyAI CR by kind and name.

* style(test): fix import order in trustyai_test.go

* align the trusty tests to the module architecture

* Align RHOAI TrustyAI Module operator with RHOAI manifests

* Fix Missing Components CRD Handling test

---------

Signed-off-by: Rui Vieira <ruidevieira@googlemail.com>
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Rui Vieira <ruidevieira@googlemail.com>
Co-authored-by: Steven Tobin <stobin@redhat.com>
Co-authored-by: Marián Macik <mmacik@redhat.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants