From 2778fe5b90801ad2aaf0638fa27514f32244327f Mon Sep 17 00:00:00 2001 From: Haoyu Sun Date: Thu, 4 Jun 2026 10:16:20 +0200 Subject: [PATCH] feat: add agentic workflow CRDs with sync tool Signed-off-by: Haoyu Sun --- Makefile | 12 +- README.md | 18 + .../agentic.openshift.io_agents.yaml | 232 ++ .../agentic.openshift.io_analysisresults.yaml | 631 +++++ ...agentic.openshift.io_approvalpolicies.yaml | 128 + ...gentic.openshift.io_escalationresults.yaml | 189 ++ ...agentic.openshift.io_executionresults.yaml | 270 ++ .../agentic.openshift.io_llmproviders.yaml | 442 ++++ ...gentic.openshift.io_proposalapprovals.yaml | 308 +++ .../agentic.openshift.io_proposals.yaml | 2284 +++++++++++++++++ ...ntic.openshift.io_verificationresults.yaml | 241 ++ ...tspeed-operator.clusterserviceversion.yaml | 177 +- .../bases/agentic.openshift.io_agents.yaml | 226 ++ .../agentic.openshift.io_analysisresults.yaml | 625 +++++ ...agentic.openshift.io_approvalpolicies.yaml | 122 + ...gentic.openshift.io_escalationresults.yaml | 183 ++ ...agentic.openshift.io_executionresults.yaml | 264 ++ .../agentic.openshift.io_llmproviders.yaml | 436 ++++ ...gentic.openshift.io_proposalapprovals.yaml | 302 +++ .../bases/agentic.openshift.io_proposals.yaml | 2278 ++++++++++++++++ ...ntic.openshift.io_verificationresults.yaml | 235 ++ config/crd/kustomization.yaml | 9 + ...tspeed-operator.clusterserviceversion.yaml | 1 + config/samples/agentic_v1alpha1_agent.yaml | 9 + .../agentic_v1alpha1_analysisresult.yaml | 7 + .../agentic_v1alpha1_approvalpolicy.yaml | 14 + .../agentic_v1alpha1_escalationresult.yaml | 7 + .../agentic_v1alpha1_executionresult.yaml | 8 + .../samples/agentic_v1alpha1_llmprovider.yaml | 12 + config/samples/agentic_v1alpha1_proposal.yaml | 15 + .../agentic_v1alpha1_proposalapproval.yaml | 12 + .../agentic_v1alpha1_verificationresult.yaml | 8 + config/samples/kustomization.yaml | 9 + hack/sync_agentic_crds.sh | 72 + 34 files changed, 9783 insertions(+), 3 deletions(-) create mode 100644 bundle/manifests/agentic.openshift.io_agents.yaml create mode 100644 bundle/manifests/agentic.openshift.io_analysisresults.yaml create mode 100644 bundle/manifests/agentic.openshift.io_approvalpolicies.yaml create mode 100644 bundle/manifests/agentic.openshift.io_escalationresults.yaml create mode 100644 bundle/manifests/agentic.openshift.io_executionresults.yaml create mode 100644 bundle/manifests/agentic.openshift.io_llmproviders.yaml create mode 100644 bundle/manifests/agentic.openshift.io_proposalapprovals.yaml create mode 100644 bundle/manifests/agentic.openshift.io_proposals.yaml create mode 100644 bundle/manifests/agentic.openshift.io_verificationresults.yaml create mode 100644 config/crd/bases/agentic.openshift.io_agents.yaml create mode 100644 config/crd/bases/agentic.openshift.io_analysisresults.yaml create mode 100644 config/crd/bases/agentic.openshift.io_approvalpolicies.yaml create mode 100644 config/crd/bases/agentic.openshift.io_escalationresults.yaml create mode 100644 config/crd/bases/agentic.openshift.io_executionresults.yaml create mode 100644 config/crd/bases/agentic.openshift.io_llmproviders.yaml create mode 100644 config/crd/bases/agentic.openshift.io_proposalapprovals.yaml create mode 100644 config/crd/bases/agentic.openshift.io_proposals.yaml create mode 100644 config/crd/bases/agentic.openshift.io_verificationresults.yaml create mode 100644 config/samples/agentic_v1alpha1_agent.yaml create mode 100644 config/samples/agentic_v1alpha1_analysisresult.yaml create mode 100644 config/samples/agentic_v1alpha1_approvalpolicy.yaml create mode 100644 config/samples/agentic_v1alpha1_escalationresult.yaml create mode 100644 config/samples/agentic_v1alpha1_executionresult.yaml create mode 100644 config/samples/agentic_v1alpha1_llmprovider.yaml create mode 100644 config/samples/agentic_v1alpha1_proposal.yaml create mode 100644 config/samples/agentic_v1alpha1_proposalapproval.yaml create mode 100644 config/samples/agentic_v1alpha1_verificationresult.yaml create mode 100755 hack/sync_agentic_crds.sh diff --git a/Makefile b/Makefile index 7943debc2..384a4d545 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,10 @@ # - use environment variables to overwrite this value (e.g export VERSION=0.0.2) VERSION ?= latest +AGENTIC_OPERATOR_REPO ?= https://github.com/openshift/lightspeed-agentic-operator +AGENTIC_OPERATOR_REF ?= main +AGENTIC_CRD_DIR = config/crd/bases + # CHANNELS define the bundle channels used in the bundle. # Add a new line here if you would like to change its default config. (E.g CHANNELS = "candidate,fast,stable") # To re-generate a bundle for other specific channels without changing the standard setup, you can: @@ -64,7 +68,7 @@ OPERATOR_SDK_VERSION ?= v1.36.1 IMG ?= $(IMAGE_TAG_BASE):$(VERSION) export IMG # ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. -ENVTEST_K8S_VERSION = 1.27.1 +ENVTEST_K8S_VERSION = 1.32.0 # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) ifeq (,$(shell go env GOBIN)) @@ -117,6 +121,10 @@ E2E_GO_TAGS := exclude_graphdriver_btrfs,containers_image_openpgp manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. $(CONTROLLER_GEN) rbac:roleName=manager-role crd:allowDangerousTypes=true webhook $(CONTROLLER_GEN_PATHS) output:crd:artifacts:config=config/crd/bases +.PHONY: sync-agentic-crds +sync-agentic-crds: ## Fetch agentic CRDs from lightspeed-agentic-operator at pinned ref. + hack/sync_agentic_crds.sh $(AGENTIC_OPERATOR_REPO) $(AGENTIC_OPERATOR_REF) $(AGENTIC_CRD_DIR) + .PHONY: generate generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" $(CONTROLLER_GEN_PATHS) @@ -401,7 +409,7 @@ endif ## to set the default channel, use the DEFAULT_CHANNEL variable ## to use image digests instead of version tag, set the USE_IMAGE_DIGESTS variable to true .PHONY: bundle -bundle: manifests kustomize operator-sdk yq jq ## Generate bundle manifests and metadata, then validate generated files. +bundle: manifests sync-agentic-crds kustomize operator-sdk yq jq ## Generate bundle manifests and metadata, then validate generated files. YQ=$(YQ) JQ=$(JQ) BUNDLE_GEN_FLAGS="$(BUNDLE_GEN_FLAGS)" ./hack/update_bundle.sh -v $(BUNDLE_TAG) -i related_images.json parking: diff --git a/README.md b/README.md index f685d3b9c..62cab61ef 100644 --- a/README.md +++ b/README.md @@ -231,6 +231,24 @@ conversationCache: type: postgres ``` +### Syncing Agentic CRDs + +The OLM bundle includes CRDs from the [lightspeed-agentic-operator](https://github.com/openshift/lightspeed-agentic-operator). These are fetched via a make target — do not hand-edit the agentic CRD or sample files. + +To sync agentic CRDs and samples from the pinned ref (defaults to `main`): + +```shell +make sync-agentic-crds +``` + +To sync from a specific tag or commit: + +```shell +make sync-agentic-crds AGENTIC_OPERATOR_REF=v0.1.0 +``` + +The pinned ref is controlled by `AGENTIC_OPERATOR_REF` in the Makefile. This target is also run automatically as part of `make bundle`. + ### Modifying the API definitions If you have updated the API definitions, you must update the CRD manifests with the following command diff --git a/bundle/manifests/agentic.openshift.io_agents.yaml b/bundle/manifests/agentic.openshift.io_agents.yaml new file mode 100644 index 000000000..379b97bf0 --- /dev/null +++ b/bundle/manifests/agentic.openshift.io_agents.yaml @@ -0,0 +1,232 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + creationTimestamp: null + name: agents.agentic.openshift.io +spec: + group: agentic.openshift.io + names: + kind: Agent + listKind: AgentList + plural: agents + singular: agent + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .spec.llmProvider.name + name: LLM + type: string + - jsonPath: .spec.model + name: Model + type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: "Agent defines a cluster-scoped agent tier (e.g., \"default\", + \"smart\", \"fast\").\nThe cluster admin creates Agent resources to configure + LLM infrastructure\nand runtime settings. Proposals reference agents by + name per step.\n\nAgent is cluster-scoped. The metadata.name serves as the + tier identifier.\nThe \"default\" agent must exist; \"smart\" and \"fast\" + are optional (the\noperator auto-links to \"default\" if absent).\n\nExample + — a high-capability agent tier:\n\n\tapiVersion: agentic.openshift.io/v1alpha1\n\tkind: + Agent\n\tmetadata:\n\t name: smart\n\tspec:\n\t llmProvider:\n\t name: + vertex-ai\n\t model: claude-opus-4-6\n\t timeouts:\n\t analysisSeconds: + 300\n\t executionSeconds: 600\n\t maxTurns: 200\n\nExample — a fast, + cost-efficient agent tier:\n\n\tapiVersion: agentic.openshift.io/v1alpha1\n\tkind: + Agent\n\tmetadata:\n\t name: fast\n\tspec:\n\t llmProvider:\n\t name: + vertex-ai\n\t model: claude-haiku-4-5\n\t timeouts:\n\t analysisSeconds: + 120\n\t executionSeconds: 300\n\t maxTurns: 100" + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec defines the desired state of Agent. + properties: + llmProvider: + description: |- + llmProvider references a cluster-scoped LLMProvider CR that supplies the + LLM backend for this agent tier. + properties: + name: + description: name of the LLMProvider. Must be a valid RFC 1123 + DNS subdomain. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS subdomain: lowercase alphanumeric + characters, hyphens, and dots' + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + required: + - name + type: object + maxTurns: + description: |- + maxTurns is the maximum number of tool-use turns the agent may take + in a single step invocation. Prevents runaway loops. + When omitted, the agent sandbox uses its built-in default. + Minimum 1, maximum 500. + format: int32 + maximum: 500 + minimum: 1 + type: integer + model: + description: |- + model is the LLM model identifier as recognized by the provider + (e.g., "claude-opus-4-6", "claude-haiku-4-5", "gpt-4o"). + Must start with an alphanumeric character and may contain + alphanumerics, dots, hyphens, underscores, slashes, colons, + and at-signs. Maximum 256 characters. + maxLength: 256 + minLength: 1 + type: string + x-kubernetes-validations: + - message: model must start with an alphanumeric character and contain + only alphanumerics, dots, hyphens, underscores, slashes, colons, + and at-signs + rule: self.matches('^[a-zA-Z0-9][a-zA-Z0-9._\\-/:@]*$') + timeouts: + description: |- + timeouts configures per-step and per-turn timeout limits. + When omitted, the agent sandbox uses its built-in defaults. + minProperties: 1 + properties: + analysisSeconds: + description: analysisSeconds is the timeout for the analysis step + in seconds. + format: int32 + maximum: 3600 + minimum: 1 + type: integer + chatSeconds: + description: chatSeconds is the timeout for each chat turn with + the LLM in seconds. + format: int32 + maximum: 600 + minimum: 1 + type: integer + executionSeconds: + description: executionSeconds is the timeout for the execution + step in seconds. + format: int32 + maximum: 3600 + minimum: 1 + type: integer + verificationSeconds: + description: verificationSeconds is the timeout for the verification + step in seconds. + format: int32 + maximum: 3600 + minimum: 1 + type: integer + type: object + required: + - llmProvider + - model + type: object + status: + description: status defines the observed state of Agent. + minProperties: 1 + properties: + conditions: + description: |- + conditions represent the latest available observations of the + Agent's state. The Ready condition summarizes whether all + referenced resources (LLMProvider, Secrets) are present. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/bundle/manifests/agentic.openshift.io_analysisresults.yaml b/bundle/manifests/agentic.openshift.io_analysisresults.yaml new file mode 100644 index 000000000..85a085a47 --- /dev/null +++ b/bundle/manifests/agentic.openshift.io_analysisresults.yaml @@ -0,0 +1,631 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + creationTimestamp: null + name: analysisresults.agentic.openshift.io +spec: + group: agentic.openshift.io + names: + kind: AnalysisResult + listKind: AnalysisResultList + plural: analysisresults + singular: analysisresult + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.proposalName + name: Proposal + type: string + - jsonPath: .status.conditions[?(@.type=="Completed")].reason + name: Outcome + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + AnalysisResult records the output of a single analysis step execution. + Created by the operator after the analysis agent completes. Owned by + the parent Proposal for garbage collection. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec contains the immutable identity fields for this result. + properties: + proposalName: + description: proposalName is the name of the parent Proposal in the + same namespace. + maxLength: 253 + minLength: 1 + type: string + required: + - proposalName + type: object + x-kubernetes-validations: + - message: spec is immutable + rule: self == oldSelf + status: + description: status contains result data and conditions. + minProperties: 1 + properties: + conditions: + description: conditions track the lifecycle of this result. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + failureReason: + description: failureReason is populated when the step failed due to + a system error. + maxLength: 8192 + minLength: 1 + type: string + options: + description: options contains the remediation options returned by + the analysis agent. + items: + description: |- + RemediationOption represents a single remediation approach produced by + the analysis agent. The agent may return multiple options, each with + its own diagnosis, remediation plan, verification strategy, and RBAC + requirements. When the user approves execution, the operator trims + the AnalysisResult to keep only the approved option and uses its + RBAC and plan for the execution step. + + The components field is an extensibility point for adapter-specific UI + data. For example, an ACS adapter might include violation details or + affected deployment information as components that the console plugin + renders with custom components. + properties: + components: + description: |- + components contains optional adapter-defined structured data whose + shape is determined by spec.analysisOutput.schema on the Proposal. + The operator passes this through to the AnalysisResult CR; the + console renders it using adapter-specific UI components. + x-kubernetes-preserve-unknown-fields: true + diagnosis: + description: |- + diagnosis contains the root cause analysis specific to this option. + Present when analysisOutput mode is Default (or omitted). Omitted + when mode is Minimal. + properties: + confidence: + description: |- + confidence is the agent's self-assessed confidence in its diagnosis. + Higher confidence generally correlates with clearer symptoms and + more deterministic root causes. + enum: + - Low + - Medium + - High + type: string + rootCause: + description: |- + rootCause is a concise Markdown-formatted description of the identified + root cause (e.g., "OOMKilled due to memory limit of 256Mi"). + Maximum 1024 characters. + maxLength: 1024 + minLength: 1 + type: string + summary: + description: |- + summary is a Markdown-formatted diagnosis summary explaining the + problem, its symptoms, and the agent's findings. Maximum 8192 characters. + maxLength: 8192 + minLength: 1 + type: string + required: + - confidence + - rootCause + - summary + type: object + proposal: + description: |- + proposal contains the remediation plan for this option. + Present when analysisOutput mode is Default (or omitted). Omitted + when mode is Minimal without an execution step. + properties: + actions: + description: |- + actions is the ordered list of discrete actions the agent proposes. + Maximum 50 items. + items: + description: |- + ProposedAction describes a single discrete action the analysis agent + recommends as part of its remediation plan. Actions are displayed to + the user after analysis for review before approval. + properties: + description: + description: |- + description is a Markdown-formatted explanation of what this action + will do (e.g., "Increase memory limit from 256Mi to 512Mi"). + Maximum 4096 characters. + maxLength: 4096 + minLength: 1 + type: string + type: + description: |- + type is the action category (e.g., "patch", "scale", "restart", + "create", "delete", "rollout"). Free-form string to allow agents + to express domain-specific action types. Must be 1-256 characters. + maxLength: 256 + minLength: 1 + type: string + required: + - description + - type + type: object + maxItems: 50 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + description: + description: |- + description is a Markdown-formatted summary of the overall remediation + approach. Maximum 8192 characters. + maxLength: 8192 + minLength: 1 + type: string + estimatedImpact: + description: |- + estimatedImpact is a Markdown-formatted description of the expected + impact of the remediation on the system + (e.g., "Brief pod restart, ~30s downtime"). + Maximum 1024 characters. + maxLength: 1024 + minLength: 1 + type: string + reversible: + description: |- + reversible indicates whether the remediation can be rolled back + if something goes wrong. See rollbackPlan for details. + Must be one of: Reversible, Irreversible, Partial. + enum: + - Reversible + - Irreversible + - Partial + type: string + risk: + description: |- + risk is the agent's assessment of how risky the remediation is. + Critical-risk proposals typically require explicit human review. + enum: + - Low + - Medium + - High + - Critical + type: string + rollbackPlan: + description: |- + rollbackPlan describes how to undo the remediation if execution fails + or causes unexpected issues. Only the execution step mutates cluster + state, so rollback lives here alongside the actions it would undo. + properties: + command: + description: |- + command is the rollback command or steps to execute. + Maximum 4096 characters. + maxLength: 4096 + minLength: 1 + type: string + description: + description: |- + description is a Markdown-formatted explanation of the rollback strategy. + Must be 1-4096 characters. + maxLength: 4096 + minLength: 1 + type: string + required: + - description + type: object + required: + - actions + - description + - estimatedImpact + - risk + type: object + rbac: + description: |- + rbac contains the RBAC permissions the execution agent will need. + The operator's policy engine validates these before creating the + actual Kubernetes RBAC resources. Omitted for advisory-only options. + minProperties: 1 + properties: + clusterScoped: + description: |- + clusterScoped are rules that will be applied via ClusterRole + + ClusterRoleBinding. Used when the agent needs cross-namespace or + non-namespaced resource access (e.g., reading nodes, CRDs). + Maximum 50 items. + items: + description: |- + RBACRule describes a single RBAC permission that the analysis agent + requests for the execution step. The operator's policy engine validates + these requests against a 6-layer defense model before creating the + actual Role/ClusterRole bindings. Each rule must include a justification + so that users and policy can audit why the permission is needed. + properties: + apiGroups: + description: |- + apiGroups are the API groups for this rule (e.g., "", "apps", "batch"). + The empty string "" represents the core API group (pods, services, etc.). + Maximum 20 items, each up to 253 characters. + items: + maxLength: 253 + type: string + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + justification: + description: |- + justification is a Markdown-formatted explanation of why this + permission is needed for the remediation + (e.g., "Need to patch deployment to increase memory limit"). + Required for audit and policy enforcement. Maximum 1024 characters. + maxLength: 1024 + minLength: 1 + type: string + namespace: + description: |- + namespace is the target namespace for namespace-scoped rules. + Must match one of the proposal's targetNamespaces. Ignored for + cluster-scoped rules. Validation is deferred to the operator's + policy engine at runtime. Must be a valid RFC 1123 DNS label. + maxLength: 63 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS label: lowercase alphanumeric + characters and hyphens, starting with an alphabetic + character and ending with an alphanumeric character' + rule: '!format.dns1123Label().validate(self).hasValue()' + resourceNames: + description: |- + resourceNames restricts the rule to specific named resources. + When empty, the rule applies to all resources of the given type. + Maximum 50 items. + items: + maxLength: 253 + minLength: 1 + type: string + maxItems: 50 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + resources: + description: |- + resources are the resource types (e.g., "pods", "deployments"). + Maximum 20 items. + items: + maxLength: 253 + minLength: 1 + type: string + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + verbs: + description: |- + verbs are the allowed operations (e.g., "get", "patch", "delete"). + Maximum 10 items. + items: + maxLength: 63 + minLength: 1 + type: string + maxItems: 10 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + required: + - apiGroups + - justification + - resources + - verbs + type: object + maxItems: 50 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + namespaceScoped: + description: |- + namespaceScoped are rules that will be applied via Role + RoleBinding + in the proposal's target namespaces. These are the most common rules. + Maximum 50 items. + items: + description: |- + RBACRule describes a single RBAC permission that the analysis agent + requests for the execution step. The operator's policy engine validates + these requests against a 6-layer defense model before creating the + actual Role/ClusterRole bindings. Each rule must include a justification + so that users and policy can audit why the permission is needed. + properties: + apiGroups: + description: |- + apiGroups are the API groups for this rule (e.g., "", "apps", "batch"). + The empty string "" represents the core API group (pods, services, etc.). + Maximum 20 items, each up to 253 characters. + items: + maxLength: 253 + type: string + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + justification: + description: |- + justification is a Markdown-formatted explanation of why this + permission is needed for the remediation + (e.g., "Need to patch deployment to increase memory limit"). + Required for audit and policy enforcement. Maximum 1024 characters. + maxLength: 1024 + minLength: 1 + type: string + namespace: + description: |- + namespace is the target namespace for namespace-scoped rules. + Must match one of the proposal's targetNamespaces. Ignored for + cluster-scoped rules. Validation is deferred to the operator's + policy engine at runtime. Must be a valid RFC 1123 DNS label. + maxLength: 63 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS label: lowercase alphanumeric + characters and hyphens, starting with an alphabetic + character and ending with an alphanumeric character' + rule: '!format.dns1123Label().validate(self).hasValue()' + resourceNames: + description: |- + resourceNames restricts the rule to specific named resources. + When empty, the rule applies to all resources of the given type. + Maximum 50 items. + items: + maxLength: 253 + minLength: 1 + type: string + maxItems: 50 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + resources: + description: |- + resources are the resource types (e.g., "pods", "deployments"). + Maximum 20 items. + items: + maxLength: 253 + minLength: 1 + type: string + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + verbs: + description: |- + verbs are the allowed operations (e.g., "get", "patch", "delete"). + Maximum 10 items. + items: + maxLength: 63 + minLength: 1 + type: string + maxItems: 10 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + required: + - apiGroups + - justification + - resources + - verbs + type: object + maxItems: 50 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + type: object + summary: + description: |- + summary is an optional Markdown-formatted one-line summary for + collapsed views in the console UI. Maximum 1024 characters. + maxLength: 1024 + minLength: 1 + type: string + title: + description: |- + title is a short Markdown-formatted name for this option + (e.g., "Increase memory limit", "Restart with backoff"). + Must be 1-256 characters. + maxLength: 256 + minLength: 1 + type: string + verification: + description: |- + verification contains the verification plan. Omitted when + verification is skipped in the workflow. + properties: + description: + description: |- + description is a Markdown-formatted summary of the verification approach. + Maximum 4096 characters. + maxLength: 4096 + minLength: 1 + type: string + steps: + description: |- + steps is the ordered list of verification checks to run. + Maximum 20 items. + items: + description: |- + VerificationStep describes a single verification check that the + verification agent should run after execution. Populated by the + analysis agent as part of the RemediationOption. + properties: + command: + description: |- + command is the command or API call to run for this check + (e.g., "oc get pod -n production -l app=web -o jsonpath='{.items[0].status.phase}'"). + Maximum 4096 characters. + maxLength: 4096 + minLength: 1 + type: string + expected: + description: |- + expected is the expected output or condition + (e.g., "Running", "ready=true"). Maximum 1024 characters. + maxLength: 1024 + minLength: 1 + type: string + name: + description: |- + name is a short identifier for this check (e.g., "pod-running"). + Must be 1-253 characters. + maxLength: 253 + minLength: 1 + type: string + type: + description: |- + type categorizes the check (e.g., "command", "metric", "condition"). + Must be 1-256 characters. + maxLength: 256 + minLength: 1 + type: string + required: + - name + - type + type: object + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + required: + - description + type: object + required: + - title + type: object + x-kubernetes-validations: + - message: proposal is required when diagnosis is present + rule: '!has(self.diagnosis) || has(self.proposal)' + - message: diagnosis is required when proposal is present + rule: '!has(self.proposal) || has(self.diagnosis)' + maxItems: 10 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + sandbox: + description: sandbox tracks the sandbox pod used for this analysis. + properties: + claimName: + description: |- + claimName is the name of the SandboxClaim resource that owns the + sandbox pod. Maximum 253 characters. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + namespace is the namespace where the SandboxClaim and its pod live. + Must be a valid RFC 1123 DNS label. + maxLength: 63 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS label: lowercase alphanumeric + characters and hyphens, starting with an alphabetic character + and ending with an alphanumeric character' + rule: '!format.dns1123Label().validate(self).hasValue()' + required: + - claimName + - namespace + type: object + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/bundle/manifests/agentic.openshift.io_approvalpolicies.yaml b/bundle/manifests/agentic.openshift.io_approvalpolicies.yaml new file mode 100644 index 000000000..6371a55d1 --- /dev/null +++ b/bundle/manifests/agentic.openshift.io_approvalpolicies.yaml @@ -0,0 +1,128 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + creationTimestamp: null + name: approvalpolicies.agentic.openshift.io +spec: + group: agentic.openshift.io + names: + kind: ApprovalPolicy + listKind: ApprovalPolicyList + plural: approvalpolicies + singular: approvalpolicy + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: "ApprovalPolicy is a cluster-scoped singleton that configures + default\napproval behavior for proposal workflow steps. The cluster admin + creates\na single ApprovalPolicy named \"cluster\" to control which steps + auto-approve.\n\nSteps not listed in the policy default to Manual (require + explicit\nuser approval on the ProposalApproval resource).\n\nExample:\n\n\tapiVersion: + agentic.openshift.io/v1alpha1\n\tkind: ApprovalPolicy\n\tmetadata:\n\t name: + cluster\n\tspec:\n\t stages:\n\t - name: Analysis\n\t approval: + Automatic\n\t - name: Execution\n\t approval: Manual\n\t - name: + Verification\n\t approval: Automatic" + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec defines the desired approval policy. + minProperties: 1 + properties: + maxAttempts: + description: |- + maxAttempts sets the maximum number of execution retry attempts + allowed for proposals. When verification fails, the operator retries + execution up to this limit before escalating. Defaults to 1 if omitted. + format: int32 + maximum: 3 + minimum: 1 + type: integer + maxConcurrentProposals: + default: 5 + description: |- + maxConcurrentProposals sets the maximum number of proposals the + operator reconciles concurrently. Higher values allow more proposals + to run in parallel but consume more cluster resources. + Defaults to 5 if omitted. + format: int32 + maximum: 20 + minimum: 1 + type: integer + stages: + description: |- + stages configures the approval mode for each workflow step. + Omitted steps default to Manual. + items: + description: ApprovalPolicyStage configures the approval mode for + a single workflow step. + properties: + approval: + description: |- + approval controls whether this step auto-approves or requires + explicit user approval on the ProposalApproval resource. + Allowed values: Automatic (step runs without user approval), + Manual (step waits for explicit approval on ProposalApproval). + enum: + - Automatic + - Manual + type: string + name: + description: |- + name is the workflow step this policy applies to. + Allowed values: Analysis, Execution, Verification, Escalation. + enum: + - Analysis + - Execution + - Verification + - Escalation + type: string + required: + - approval + - name + type: object + maxItems: 4 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + x-kubernetes-validations: + - message: ApprovalPolicy must be named 'cluster' (singleton) + rule: self.metadata.name == 'cluster' + served: true + storage: true + subresources: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/bundle/manifests/agentic.openshift.io_escalationresults.yaml b/bundle/manifests/agentic.openshift.io_escalationresults.yaml new file mode 100644 index 000000000..777de67cc --- /dev/null +++ b/bundle/manifests/agentic.openshift.io_escalationresults.yaml @@ -0,0 +1,189 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + creationTimestamp: null + name: escalationresults.agentic.openshift.io +spec: + group: agentic.openshift.io + names: + kind: EscalationResult + listKind: EscalationResultList + plural: escalationresults + singular: escalationresult + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.proposalName + name: Proposal + type: string + - jsonPath: .status.conditions[?(@.type=="Completed")].reason + name: Outcome + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + EscalationResult records the output of the escalation step. Created by + the operator after the escalation agent completes. Owned by the parent + Proposal for garbage collection. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec contains the immutable identity fields for this result. + properties: + proposalName: + description: proposalName is the name of the parent Proposal in the + same namespace. + maxLength: 253 + minLength: 1 + type: string + required: + - proposalName + type: object + x-kubernetes-validations: + - message: spec is immutable + rule: self == oldSelf + status: + description: status contains result data and conditions. + minProperties: 1 + properties: + conditions: + description: conditions track the lifecycle of this result. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + content: + description: content is freeform escalation content produced by the + agent. + maxLength: 65536 + minLength: 1 + type: string + failureReason: + description: failureReason is populated when the step failed due to + a system error. + maxLength: 8192 + minLength: 1 + type: string + sandbox: + description: sandbox tracks the sandbox pod used for this escalation. + properties: + claimName: + description: |- + claimName is the name of the SandboxClaim resource that owns the + sandbox pod. Maximum 253 characters. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + namespace is the namespace where the SandboxClaim and its pod live. + Must be a valid RFC 1123 DNS label. + maxLength: 63 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS label: lowercase alphanumeric + characters and hyphens, starting with an alphabetic character + and ending with an alphanumeric character' + rule: '!format.dns1123Label().validate(self).hasValue()' + required: + - claimName + - namespace + type: object + summary: + description: summary is a Markdown-formatted escalation summary. + maxLength: 32768 + minLength: 1 + type: string + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/bundle/manifests/agentic.openshift.io_executionresults.yaml b/bundle/manifests/agentic.openshift.io_executionresults.yaml new file mode 100644 index 000000000..62f78ace4 --- /dev/null +++ b/bundle/manifests/agentic.openshift.io_executionresults.yaml @@ -0,0 +1,270 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + creationTimestamp: null + name: executionresults.agentic.openshift.io +spec: + group: agentic.openshift.io + names: + kind: ExecutionResult + listKind: ExecutionResultList + plural: executionresults + singular: executionresult + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.proposalName + name: Proposal + type: string + - jsonPath: .spec.retryIndex + name: Retry + type: integer + - jsonPath: .status.conditions[?(@.type=="Completed")].reason + name: Outcome + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + ExecutionResult records the output of a single execution step execution. + Created by the operator after the execution agent completes. Owned by + the parent Proposal for garbage collection. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec contains the immutable identity fields for this result. + properties: + proposalName: + description: proposalName is the name of the parent Proposal in the + same namespace. + maxLength: 253 + minLength: 1 + type: string + retryIndex: + description: |- + retryIndex is the 0-based retry index within the current analysis. + First execution has retryIndex 0, first retry has retryIndex 1, etc. + format: int32 + maximum: 2 + minimum: 0 + type: integer + required: + - proposalName + - retryIndex + type: object + x-kubernetes-validations: + - message: spec is immutable + rule: self == oldSelf + status: + description: status contains result data and conditions. + minProperties: 1 + properties: + actionsTaken: + description: actionsTaken lists what the agent did. + items: + description: |- + ExecutionAction describes a single action taken by the execution agent + during the execution step. These are recorded in ExecutionStepStatus + to provide an audit trail of what the agent actually did. + properties: + description: + description: |- + description is a Markdown-formatted explanation of what the agent did + (e.g., "Patched deployment/web to set memory limit to 512Mi"). + Maximum 4096 characters. + maxLength: 4096 + minLength: 1 + type: string + error: + description: |- + error is the error message if the action failed. + Maximum 8192 characters. + maxLength: 8192 + minLength: 1 + type: string + outcome: + description: |- + outcome indicates whether this individual action succeeded. + Must be one of: Succeeded, Failed. + enum: + - Succeeded + - Failed + type: string + output: + description: |- + output is the command output or API response from the action. + Maximum 32768 characters. + maxLength: 32768 + minLength: 1 + type: string + type: + description: |- + type is the action category (e.g., "patch", "scale", "restart"). + Maximum 256 characters. + maxLength: 256 + minLength: 1 + type: string + required: + - description + - outcome + - type + type: object + maxItems: 100 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + conditions: + description: conditions track the lifecycle of this result. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + failureReason: + description: failureReason is populated when the step failed due to + a system error. + maxLength: 8192 + minLength: 1 + type: string + sandbox: + description: sandbox tracks the sandbox pod used for this execution. + properties: + claimName: + description: |- + claimName is the name of the SandboxClaim resource that owns the + sandbox pod. Maximum 253 characters. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + namespace is the namespace where the SandboxClaim and its pod live. + Must be a valid RFC 1123 DNS label. + maxLength: 63 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS label: lowercase alphanumeric + characters and hyphens, starting with an alphabetic character + and ending with an alphanumeric character' + rule: '!format.dns1123Label().validate(self).hasValue()' + required: + - claimName + - namespace + type: object + verification: + description: |- + verification is the lightweight inline verification the execution + agent performs immediately after completing its actions. + properties: + conditionOutcome: + description: |- + conditionOutcome indicates whether the target condition improved + after the remediation (e.g., pod is no longer CrashLoopBackOff). + Must be one of: Improved, Unchanged, Degraded. + enum: + - Improved + - Unchanged + - Degraded + type: string + summary: + description: |- + summary is a Markdown-formatted summary of the inline verification. + Maximum 4096 characters. + maxLength: 4096 + minLength: 1 + type: string + required: + - conditionOutcome + - summary + type: object + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/bundle/manifests/agentic.openshift.io_llmproviders.yaml b/bundle/manifests/agentic.openshift.io_llmproviders.yaml new file mode 100644 index 000000000..6aee024dc --- /dev/null +++ b/bundle/manifests/agentic.openshift.io_llmproviders.yaml @@ -0,0 +1,442 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + creationTimestamp: null + name: llmproviders.agentic.openshift.io +spec: + group: agentic.openshift.io + names: + kind: LLMProvider + listKind: LLMProviderList + plural: llmproviders + singular: llmprovider + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .spec.type + name: Type + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: "LLMProvider defines an LLM provider configuration. It is the + first link in\nthe CRD chain (LLMProvider -> Agent -> Workflow -> Proposal) + and is\nreferenced by Agent resources via spec.llmProvider.\n\nLLMProvider + is cluster-scoped — the cluster admin manages LLM infrastructure\ncentrally. + The operator uses the credentials to configure the LLM client\ninside agent + sandbox pods. The model is specified on the Agent CR, allowing\nmultiple + agents to share one LLMProvider with different models.\n\nTypically you + create one provider per backend (e.g., one for Vertex AI)\nand then reference + it from multiple Agent resources with different models.\n\nExample — a Vertex + AI provider (model specified on Agent, not here):\n\n\tapiVersion: agentic.openshift.io/v1alpha1\n\tkind: + LLMProvider\n\tmetadata:\n\t name: vertex-ai\n\tspec:\n\t type: GoogleCloudVertex\n\t + \ googleCloudVertex:\n\t credentialsSecret:\n\t name: llm-credentials\n\t + \ projectID: my-gcp-project\n\t region: us-central1\n\t modelProvider: + Anthropic" + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec defines the desired state of LLMProvider. + properties: + anthropic: + description: |- + anthropic contains Anthropic-specific configuration. + Required when type is "Anthropic". + properties: + credentialsSecret: + description: |- + credentialsSecret references a Secret in the operator namespace + (openshift-lightspeed) containing the Anthropic API credentials. + The Secret must contain the key ANTHROPIC_API_KEY. + properties: + name: + description: name of the Secret. Must be a valid RFC 1123 + DNS subdomain. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS subdomain: lowercase alphanumeric + characters, hyphens, and dots' + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + required: + - name + type: object + url: + description: |- + url is an optional override for the Anthropic API endpoint. + Only needed for custom deployments or API proxies. + Must be a valid HTTP or HTTPS URL with a hostname. Paths and query + parameters are allowed. Fragments and userinfo are not permitted. + Maximum 2048 characters. + maxLength: 2048 + minLength: 1 + type: string + x-kubernetes-validations: + - message: must use http or https scheme + rule: isURL(self) && url(self).getScheme() in ['http', 'https'] + - message: must include a hostname + rule: isURL(self) && url(self).getHostname() != '' + - message: userinfo is not allowed in URL + rule: '!self.contains(''@'')' + - message: fragments are not allowed in URL + rule: '!self.contains(''#'')' + required: + - credentialsSecret + type: object + awsBedrock: + description: |- + awsBedrock contains AWS Bedrock-specific configuration. + Required when type is "AWSBedrock". + properties: + credentialsSecret: + description: |- + credentialsSecret references a Secret in the operator namespace + (openshift-lightspeed) containing AWS credentials. The Secret must + contain the keys AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY. + properties: + name: + description: name of the Secret. Must be a valid RFC 1123 + DNS subdomain. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS subdomain: lowercase alphanumeric + characters, hyphens, and dots' + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + required: + - name + type: object + region: + description: |- + region is the AWS region for the Bedrock endpoint. + Must begin with a lowercase letter and end with a lowercase + alphanumeric character. May contain lowercase letters, digits, + and hyphens (e.g., "us-east-1", "eu-west-2", "ap-southeast-1"). + maxLength: 63 + minLength: 2 + type: string + x-kubernetes-validations: + - message: region must contain only lowercase letters, digits, + and hyphens, start with a letter, and not end with a hyphen + rule: self.matches('^[a-z][a-z0-9-]*[a-z0-9]$') + url: + description: |- + url is an optional override for the AWS Bedrock API endpoint. + Only needed for custom deployments or API proxies. + Must be a valid HTTP or HTTPS URL with a hostname. Paths and query + parameters are allowed. Fragments and userinfo are not permitted. + Maximum 2048 characters. + maxLength: 2048 + minLength: 1 + type: string + x-kubernetes-validations: + - message: must use http or https scheme + rule: isURL(self) && url(self).getScheme() in ['http', 'https'] + - message: must include a hostname + rule: isURL(self) && url(self).getHostname() != '' + - message: userinfo is not allowed in URL + rule: '!self.contains(''@'')' + - message: fragments are not allowed in URL + rule: '!self.contains(''#'')' + required: + - credentialsSecret + - region + type: object + azureOpenAI: + description: |- + azureOpenAI contains Azure OpenAI Service-specific configuration. + Required when type is "AzureOpenAI". + properties: + apiVersion: + description: |- + apiVersion is the Azure OpenAI API version. Azure API versions use + a date-based format: YYYY-MM-DD with an optional "-preview" suffix + (e.g., "2024-02-01", "2024-08-01-preview"). + When omitted, the SDK default is used. + maxLength: 32 + minLength: 1 + type: string + x-kubernetes-validations: + - message: apiVersion must be a date in YYYY-MM-DD format with + an optional -preview suffix + rule: self.matches('^[0-9]{4}-[0-9]{2}-[0-9]{2}(-preview)?$') + credentialsSecret: + description: |- + credentialsSecret references a Secret in the operator namespace + (openshift-lightspeed) containing the Azure OpenAI API credentials. + The Secret must contain the key AZURE_OPENAI_API_KEY. + properties: + name: + description: name of the Secret. Must be a valid RFC 1123 + DNS subdomain. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS subdomain: lowercase alphanumeric + characters, hyphens, and dots' + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + required: + - name + type: object + endpoint: + description: |- + endpoint is the Azure OpenAI resource endpoint + (e.g., "https://my-resource.openai.azure.com"). + Must be a valid HTTP or HTTPS URL with a hostname. Paths and query + parameters are allowed. Fragments and userinfo are not permitted. + Maximum 2048 characters. + maxLength: 2048 + minLength: 1 + type: string + x-kubernetes-validations: + - message: must use http or https scheme + rule: isURL(self) && url(self).getScheme() in ['http', 'https'] + - message: must include a hostname + rule: isURL(self) && url(self).getHostname() != '' + - message: userinfo is not allowed in URL + rule: '!self.contains(''@'')' + - message: fragments are not allowed in URL + rule: '!self.contains(''#'')' + url: + description: |- + url is an optional override for the Azure OpenAI API endpoint. + Only needed for custom deployments or API proxies. This is separate + from the required 'endpoint' field which identifies the Azure resource. + Must be a valid HTTP or HTTPS URL with a hostname. Paths and query + parameters are allowed. Fragments and userinfo are not permitted. + Maximum 2048 characters. + maxLength: 2048 + minLength: 1 + type: string + x-kubernetes-validations: + - message: must use http or https scheme + rule: isURL(self) && url(self).getScheme() in ['http', 'https'] + - message: must include a hostname + rule: isURL(self) && url(self).getHostname() != '' + - message: userinfo is not allowed in URL + rule: '!self.contains(''@'')' + - message: fragments are not allowed in URL + rule: '!self.contains(''#'')' + required: + - credentialsSecret + - endpoint + type: object + googleCloudVertex: + description: |- + googleCloudVertex contains Google Cloud Vertex AI-specific configuration. + Required when type is "GoogleCloudVertex". + properties: + credentialsSecret: + description: |- + credentialsSecret references a Secret in the operator namespace + (openshift-lightspeed) containing a GCP service account JSON key. + The Secret must contain the key GOOGLE_APPLICATION_CREDENTIALS. + properties: + name: + description: name of the Secret. Must be a valid RFC 1123 + DNS subdomain. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS subdomain: lowercase alphanumeric + characters, hyphens, and dots' + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + required: + - name + type: object + modelProvider: + description: |- + modelProvider selects which model provider stack talks to Vertex AI (required). + "Anthropic" uses Anthropic models on Vertex; "Google" uses Google models on Vertex; + "OpenAI" uses OpenAI-compatible models on Vertex. + Enum is defined on GoogleCloudVertexModelProvider. + enum: + - Anthropic + - Google + - OpenAI + type: string + projectID: + description: |- + projectID is the Google Cloud Project ID where Vertex AI is enabled. + A Project ID is a globally unique identifier that must be 6 to 30 + characters in length, can only contain lowercase letters, digits, and + hyphens, must start with a letter, and cannot end with a hyphen. + maxLength: 30 + minLength: 6 + type: string + x-kubernetes-validations: + - message: projectID must start with a lowercase letter, contain + only lowercase letters, digits, and hyphens, and cannot end + with a hyphen + rule: self.matches('^[a-z][a-z0-9-]*[a-z0-9]$') + region: + description: |- + region is the GCP region for the Vertex AI endpoint. + Must begin with a lowercase letter and end with a lowercase + alphanumeric character. May contain lowercase letters, digits, + and hyphens (e.g., "us-central1", "europe-west4", "asia-southeast1"). + maxLength: 63 + minLength: 2 + type: string + x-kubernetes-validations: + - message: region must contain only lowercase letters, digits, + and hyphens, start with a letter, and not end with a hyphen + rule: self.matches('^[a-z][a-z0-9-]*[a-z0-9]$') + url: + description: |- + url is an optional override for the Vertex AI API endpoint. + Only needed for custom deployments or API proxies. + Must be a valid HTTP or HTTPS URL with a hostname. Paths and query + parameters are allowed. Fragments and userinfo are not permitted. + Maximum 2048 characters. + maxLength: 2048 + minLength: 1 + type: string + x-kubernetes-validations: + - message: must use http or https scheme + rule: isURL(self) && url(self).getScheme() in ['http', 'https'] + - message: must include a hostname + rule: isURL(self) && url(self).getHostname() != '' + - message: userinfo is not allowed in URL + rule: '!self.contains(''@'')' + - message: fragments are not allowed in URL + rule: '!self.contains(''#'')' + required: + - credentialsSecret + - modelProvider + - projectID + - region + type: object + openAI: + description: |- + openAI contains OpenAI-specific configuration. + Required when type is "OpenAI". + properties: + credentialsSecret: + description: |- + credentialsSecret references a Secret in the operator namespace + (openshift-lightspeed) containing the OpenAI API credentials. + The Secret must contain the key OPENAI_API_KEY. + properties: + name: + description: name of the Secret. Must be a valid RFC 1123 + DNS subdomain. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS subdomain: lowercase alphanumeric + characters, hyphens, and dots' + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + required: + - name + type: object + url: + description: |- + url is an optional override for the OpenAI API endpoint. + Only needed for custom deployments or API proxies. + Must be a valid HTTP or HTTPS URL with a hostname. Paths and query + parameters are allowed. Fragments and userinfo are not permitted. + Maximum 2048 characters. + maxLength: 2048 + minLength: 1 + type: string + x-kubernetes-validations: + - message: must use http or https scheme + rule: isURL(self) && url(self).getScheme() in ['http', 'https'] + - message: must include a hostname + rule: isURL(self) && url(self).getHostname() != '' + - message: userinfo is not allowed in URL + rule: '!self.contains(''@'')' + - message: fragments are not allowed in URL + rule: '!self.contains(''#'')' + required: + - credentialsSecret + type: object + type: + description: |- + type is a required field that configures which LLM provider backend + should be used. + + Allowed values are Anthropic, GoogleCloudVertex, OpenAI, AzureOpenAI, + and AWSBedrock. + + When set to Anthropic, agents referencing this provider will use the + Anthropic API directly, and the 'anthropic' field must be configured. + + When set to GoogleCloudVertex, agents referencing this provider will + use Google Cloud Vertex AI, and the 'googleCloudVertex' field must be + configured. + + When set to OpenAI, agents referencing this provider will use an + OpenAI-compatible API, and the 'openAI' field must be configured. + + When set to AzureOpenAI, agents referencing this provider will use + the Azure OpenAI Service, and the 'azureOpenAI' field must be + configured. + + When set to AWSBedrock, agents referencing this provider will use + AWS Bedrock, and the 'awsBedrock' field must be configured. + enum: + - Anthropic + - GoogleCloudVertex + - OpenAI + - AzureOpenAI + - AWSBedrock + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: anthropic is required when type is Anthropic, and forbidden + otherwise + rule: 'self.type == ''Anthropic'' ? has(self.anthropic) : !has(self.anthropic)' + - message: googleCloudVertex is required when type is GoogleCloudVertex, + and forbidden otherwise + rule: 'self.type == ''GoogleCloudVertex'' ? has(self.googleCloudVertex) + : !has(self.googleCloudVertex)' + - message: openAI is required when type is OpenAI, and forbidden otherwise + rule: 'self.type == ''OpenAI'' ? has(self.openAI) : !has(self.openAI)' + - message: azureOpenAI is required when type is AzureOpenAI, and forbidden + otherwise + rule: 'self.type == ''AzureOpenAI'' ? has(self.azureOpenAI) : !has(self.azureOpenAI)' + - message: awsBedrock is required when type is AWSBedrock, and forbidden + otherwise + rule: 'self.type == ''AWSBedrock'' ? has(self.awsBedrock) : !has(self.awsBedrock)' + required: + - spec + type: object + served: true + storage: true + subresources: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/bundle/manifests/agentic.openshift.io_proposalapprovals.yaml b/bundle/manifests/agentic.openshift.io_proposalapprovals.yaml new file mode 100644 index 000000000..289927fd6 --- /dev/null +++ b/bundle/manifests/agentic.openshift.io_proposalapprovals.yaml @@ -0,0 +1,308 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + creationTimestamp: null + name: proposalapprovals.agentic.openshift.io +spec: + group: agentic.openshift.io + names: + kind: ProposalApproval + listKind: ProposalApprovalList + plural: proposalapprovals + singular: proposalapproval + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: "ProposalApproval tracks per-step approval state for a Proposal. + The\noperator creates it when a Proposal is created. Users update it to\napprove + or deny individual workflow steps.\n\nProposalApproval has a 1:1 relationship + with its Proposal (same name,\nsame namespace) and is owned by the Proposal + via an owner reference\nfor garbage collection.\n\nExample:\n\n\tapiVersion: + agentic.openshift.io/v1alpha1\n\tkind: ProposalApproval\n\tmetadata:\n\t + \ name: fix-crash\n\t namespace: my-namespace\n\t ownerReferences:\n\t + \ - apiVersion: agentic.openshift.io/v1alpha1\n\t kind: Proposal\n\t + \ name: fix-crash\n\tspec:\n\t stages:\n\t - type: Analysis\n\t + \ analysis: {}\n\t - type: Execution\n\t execution:\n\t option: + 0\n\t agent: fast" + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec defines the desired approval state. + minProperties: 1 + properties: + stages: + description: |- + stages lists the approved (or denied) workflow steps. Each entry is + a discriminated union keyed by type. Users add stages one at a time + via patch as they approve each step. + items: + description: |- + ApprovalStage is a discriminated union representing approval for one + workflow step. Presence in spec.stages indicates approval; absence means + not yet approved (controller checks ApprovalPolicy for auto-approve). + properties: + analysis: + description: |- + analysis contains approval parameters for the analysis step. + Required when type is Analysis. + minProperties: 1 + properties: + agent: + default: default + description: agent is the Agent CR for this step. Defaults + to "default". + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS subdomain: lowercase alphanumeric + characters, hyphens, and dots' + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + type: object + decision: + description: |- + decision indicates whether this stage is approved or denied. + Denying any stage terminates the entire proposal, even if + earlier stages were already approved. Once set to Denied, + it cannot be changed. + enum: + - Approved + - Denied + type: string + escalation: + description: |- + escalation contains approval parameters for the escalation step. + Required when type is Escalation. + minProperties: 1 + properties: + agent: + default: default + description: agent is the Agent CR for this step. Defaults + to "default". + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS subdomain: lowercase alphanumeric + characters, hyphens, and dots' + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + type: object + execution: + description: |- + execution contains approval parameters for the execution step. + Required when type is Execution. + minProperties: 1 + properties: + agent: + default: default + description: agent is the Agent CR for this step. Defaults + to "default". + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS subdomain: lowercase alphanumeric + characters, hyphens, and dots' + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + maxAttempts: + description: |- + maxAttempts is the number of execution retry attempts approved + for this proposal. Must not exceed ApprovalPolicy.spec.maxAttempts. + Defaults to 1 if unset. + format: int32 + maximum: 3 + minimum: 1 + type: integer + option: + description: |- + option is the 0-based index into the analysis options array + selecting which remediation approach to execute. + format: int32 + minimum: 0 + type: integer + type: object + type: + description: type identifies which workflow step this approval + is for. + enum: + - Analysis + - Execution + - Verification + - Escalation + type: string + verification: + description: |- + verification contains approval parameters for the verification step. + Required when type is Verification. + minProperties: 1 + properties: + agent: + default: default + description: agent is the Agent CR for this step. Defaults + to "default". + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS subdomain: lowercase alphanumeric + characters, hyphens, and dots' + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + type: object + required: + - type + type: object + x-kubernetes-validations: + - message: analysis is required when type is Analysis, and forbidden + otherwise + rule: 'self.type == ''Analysis'' ? has(self.analysis) : !has(self.analysis)' + - message: execution is required when type is Execution, and forbidden + otherwise + rule: 'self.type == ''Execution'' ? has(self.execution) : !has(self.execution)' + - message: verification is required when type is Verification, and + forbidden otherwise + rule: 'self.type == ''Verification'' ? has(self.verification) + : !has(self.verification)' + - message: escalation is required when type is Escalation, and forbidden + otherwise + rule: 'self.type == ''Escalation'' ? has(self.escalation) : !has(self.escalation)' + maxItems: 4 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + x-kubernetes-validations: + - message: 'stages are append-only: existing stages cannot be removed' + rule: oldSelf.stages.all(old, self.stages.exists(s, s.type == old.type)) + - message: decisions once set cannot be changed + rule: oldSelf.stages.all(old, !(has(old.decision) && old.decision == + 'Denied') || self.stages.exists(s, s.type == old.type && has(s.decision) + && s.decision == 'Denied')) + - message: maxAttempts once set cannot be changed + rule: oldSelf.stages.all(old, old.type != 'Execution' || !has(old.execution) + || !has(old.execution.maxAttempts) || old.execution.maxAttempts == + 0 || self.stages.exists(s, s.type == 'Execution' && has(s.execution) + && has(s.execution.maxAttempts) && s.execution.maxAttempts == old.execution.maxAttempts)) + status: + description: status defines the observed approval state. + minProperties: 1 + properties: + stages: + description: stages contains the per-stage approval status set by + the controller. + items: + description: ApprovalStageStatus is the observed state of a single + approval stage. + properties: + conditions: + description: conditions for this approval stage. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + name: + description: name identifies the workflow step. + maxLength: 253 + minLength: 1 + type: string + required: + - name + type: object + maxItems: 4 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/bundle/manifests/agentic.openshift.io_proposals.yaml b/bundle/manifests/agentic.openshift.io_proposals.yaml new file mode 100644 index 000000000..fac75c57a --- /dev/null +++ b/bundle/manifests/agentic.openshift.io_proposals.yaml @@ -0,0 +1,2284 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + creationTimestamp: null + name: proposals.agentic.openshift.io +spec: + group: agentic.openshift.io + names: + kind: Proposal + listKind: ProposalList + plural: proposals + singular: proposal + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.request + name: Request + priority: 1 + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: "Proposal represents a unit of work managed by the agentic platform.\nIt + is the primary resource component teams and adapters interact with.\n\nA + Proposal defines the workflow shape inline: which steps run and which\nagent + handles each step. Analysis is always required. Omit execution\nand/or verification + to skip those steps.\n\nExample — analysis only (advisory):\n\n\tapiVersion: + agentic.openshift.io/v1alpha1\n\tkind: Proposal\n\tmetadata:\n\t name: + one-off-investigation\n\tspec:\n\t request: \"Investigate why pod foo is + crashlooping\"\n\t targetNamespaces:\n\t - lightspeed-demo\n\t tools:\n\t + \ skills:\n\t - image: registry.redhat.io/acs/acs-agentic-skills:latest\n\t + \ analysis:\n\t agent: smart\n\nExample — full remediation (analyze → + execute → verify):\n\n\tapiVersion: agentic.openshift.io/v1alpha1\n\tkind: + Proposal\n\tmetadata:\n\t name: fix-nginx-cve-2024-1234\n\t namespace: + stackrox\n\tspec:\n\t request: \"Fix CVE-2024-1234 in nginx:1.21\"\n\t + \ targetNamespaces:\n\t - lightspeed-demo\n\t tools:\n\t skills:\n\t + \ - image: registry.redhat.io/acs/acs-agentic-skills:latest\n\t requiredSecrets:\n\t + \ - name: acs-api-token\n\t mountAs:\n\t type: EnvVar\n\t + \ envVar:\n\t name: ACS_API_TOKEN\n\t analysis:\n\t + \ agent: smart\n\t execution: {}\n\t verification:\n\t agent: fast" + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec defines the desired state of Proposal. + properties: + analysis: + description: |- + analysis defines per-step configuration for the analysis step, + including which agent handles it and any per-step tools. + + Immutable: agent and per-step tools are fixed at creation. + minProperties: 1 + properties: + agent: + description: |- + agent is the name of the cluster-scoped Agent CR to use for this step. + Defaults to "default" when omitted. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS subdomain: lowercase alphanumeric + characters, hyphens, and dots' + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + tools: + description: |- + tools provides per-step tools that replace the shared spec.tools + for this step. Use this when different steps need different skills. + minProperties: 1 + properties: + mcpServers: + description: |- + mcpServers defines external MCP (Model Context Protocol) servers the + agent can connect to for additional tools and context. + items: + description: "MCPServerConfig defines the configuration + for an MCP (Model Context Protocol)\nserver that the agent + can connect to for additional tools and context.\nMCP + servers extend the agent's capabilities beyond its built-in + skills.\n\nExample — connecting to an OpenShift MCP server + with SA token auth:\n\n\tmcpServers:\n\t - name: openshift\n\t + \ url: https://mcp.openshift-lightspeed.svc:8443/sse\n\t + \ timeoutSeconds: 10\n\t headers:\n\t - name: + Authorization\n\t valueFrom:\n\t type: + ServiceAccountToken\n\nExample — connecting to an external + API with secret-based auth:\n\n\tmcpServers:\n\t - name: + pagerduty\n\t url: https://mcp-pagerduty.example.com/sse\n\t + \ headers:\n\t - name: X-API-Key\n\t valueFrom:\n\t + \ type: Secret\n\t secret:\n\t name: + pagerduty-api-key" + properties: + headers: + description: headers to send to the MCP server. Maximum + 20 items. + items: + description: |- + MCPHeader defines an HTTP header to send with every request to an + MCP server. Used for authentication and routing. + properties: + name: + description: |- + name of the header (e.g., "Authorization", "X-API-Key"). + Must be at least 1 character, containing only letters, digits, and hyphens. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: name must start with a letter and contain + only letters, digits, and hyphens + rule: self.matches('^[A-Za-z][A-Za-z0-9-]*$') + valueFrom: + description: valueFrom is the source of the header + value. + properties: + secret: + description: |- + secret references a Secret containing the header value. + Required when type is "Secret". + properties: + name: + description: name of the Secret. Must + be a valid RFC 1123 DNS subdomain. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS subdomain: + lowercase alphanumeric characters, + hyphens, and dots' + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + required: + - name + type: object + type: + description: |- + type specifies the source type for the header value. Allowed values: + - "Secret" — reads the value from a Kubernetes Secret (use for + API keys and tokens). Requires the secret field to be set. + - "ServiceAccountToken" — auto-injects a Kubernetes service account token + (for MCP servers that accept K8s auth). + - "Client" — the value is provided by the calling client at + runtime (e.g., forwarded from a user session). + enum: + - Secret + - ServiceAccountToken + - Client + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: secret is required when type is Secret, + and forbidden otherwise + rule: 'self.type == ''Secret'' ? has(self.secret) + : !has(self.secret)' + required: + - name + - valueFrom + type: object + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + name: + description: |- + name of the MCP server. Must start with a letter and contain only + lowercase alphanumeric characters and hyphens. Must be 1-253 characters. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: name must start with a lowercase letter and + contain only lowercase alphanumerics and hyphens + rule: self.matches('^[a-z][a-z0-9-]*$') + timeoutSeconds: + default: 5 + description: |- + timeoutSeconds is the per-request timeout for calls to this MCP server, + in seconds. Default is 5. + Valid range: 1-300. + format: int32 + maximum: 300 + minimum: 1 + type: integer + url: + description: |- + url of the MCP server (HTTP/HTTPS). Must be an HTTP or HTTPS URL, + maximum 2048 characters. + maxLength: 2048 + minLength: 1 + type: string + x-kubernetes-validations: + - message: url must be a valid HTTP or HTTPS URL + rule: isURL(self) && url(self).getScheme() in ['http', + 'https'] + required: + - name + - url + type: object + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + requiredSecrets: + description: |- + requiredSecrets declares Kubernetes Secrets that the sandbox pod + needs at runtime. The cluster admin creates the actual Secrets + in the same namespace as the Proposal. + items: + description: |- + SecretRequirement declares a Kubernetes Secret that the sandbox needs + at runtime. The Secret must exist in the operator namespace (where + sandbox pods run), not in the Proposal's namespace. + properties: + description: + description: |- + description explains what this secret is used for, helping the + cluster admin understand what credentials to provide. + maxLength: 1024 + minLength: 1 + type: string + mountAs: + description: mountAs specifies how the secret is exposed + in the sandbox pod. + properties: + envVar: + description: |- + envVar configures environment variable injection. + Required when type is "EnvVar". + properties: + name: + description: |- + name is the environment variable name (e.g., "GITHUB_TOKEN"). + Must be uppercase letters, digits, and underscores, starting + with a letter or underscore. + maxLength: 256 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid environment variable + name: uppercase letters, digits, and underscores, + starting with a letter or underscore' + rule: self.matches('^[A-Z_][A-Z0-9_]*$') + required: + - name + type: object + filePath: + description: |- + filePath configures file mount. + Required when type is "FilePath". + properties: + path: + description: |- + path is the absolute file path (e.g., "/etc/secrets/tls.crt"). + Must start with a forward slash. + maxLength: 512 + minLength: 2 + type: string + x-kubernetes-validations: + - message: path must be an absolute path starting + with '/' + rule: self.startsWith('/') + required: + - path + type: object + type: + description: |- + type specifies how the secret is exposed. Allowed values: "EnvVar", + "FilePath". + + When set to EnvVar, the secret value is injected as an environment + variable, and the 'envVar' field must be configured. + + When set to FilePath, the secret is mounted as a file, and the + 'filePath' field must be configured. + enum: + - EnvVar + - FilePath + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: envVar is required when type is EnvVar, and + forbidden otherwise + rule: 'self.type == ''EnvVar'' ? has(self.envVar) + : !has(self.envVar)' + - message: filePath is required when type is FilePath, + and forbidden otherwise + rule: 'self.type == ''FilePath'' ? has(self.filePath) + : !has(self.filePath)' + name: + description: |- + name of the Secret (must exist in the operator namespace). + Must be a valid RFC 1123 DNS subdomain. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS subdomain: lowercase + alphanumeric characters, hyphens, and dots' + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + required: + - mountAs + - name + type: object + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + skills: + description: |- + skills defines one or more OCI images containing skills to mount + in the agent's sandbox pod. The operator creates Kubernetes image + volumes (requires K8s 1.34+) and mounts them into the agent's + skills directory. Each image must be unique within the list. + items: + description: "SkillsSource defines an OCI image containing + skills and which paths\nwithin that image to mount. Skills + are mounted as Kubernetes image\nvolumes in the agent's + sandbox pod.\n\nEach path is mounted as a separate subPath + volumeMount, allowing\nselective composition of skills + from shared images.\n\nExample — mount specific skills + from the agentic-skills image:\n\n\tskills:\n\t - image: + registry.ci.openshift.org/ocp/5.0:agentic-skills\n\t paths:\n\t + \ - /skills/cluster-update/update-advisor" + properties: + image: + description: |- + image is the OCI image reference containing skills. + The operator mounts this as a Kubernetes image volume (requires K8s 1.34+). + Must be a valid OCI image pullspec: a domain, followed by a repository path, + ending with either a tag (:tag) or a digest (@algorithm:hex). + Must be 1-512 characters. + maxLength: 512 + minLength: 1 + type: string + x-kubernetes-validations: + - message: must start with a valid domain. valid domains + must be alphanumeric characters (lowercase and uppercase) + separated by the '.' character. + rule: self.matches('^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])((\\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]))+)?(:[0-9]+)?\\b') + - message: a valid name is required. valid names must + contain lowercase alphanumeric characters separated + only by the '.', '_', '__', '-' characters. + rule: self.find('(/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?((/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?)+)?)') + != '' + - message: must end with a digest or a tag + rule: self.find('(@.*:)') != '' || self.find(':.*$') + != '' + - message: tag must not be more than 127 characters + rule: 'self.find(''(@.*:)'') == '''' ? (self.find('':.*$'') + != '''' ? self.find('':.*$'').substring(1).size() + <= 127 : true) : true' + - message: tag is invalid. valid tags must begin with + a word character followed by word characters, '.', + or '-' + rule: 'self.find(''(@.*:)'') == '''' ? (self.find('':.*$'') + != '''' ? self.find('':.*$'').matches('':[\\w][\\w.-]*$'') + : true) : true' + - message: digest algorithm is not valid. valid algorithms + must start with an alpha character followed by alphanumeric + characters and may contain '-', '_', '+', and '.' + characters. + rule: 'self.find(''(@.*:)'') != '''' ? self.find(''(@.*:)'').matches(''(@[A-Za-z][A-Za-z0-9]*([_+.][A-Za-z][A-Za-z0-9]*)*[:])'') + : true' + - message: digest must be at least 32 characters + rule: 'self.find(''(@.*:)'') != '''' ? self.find('':.*$'').substring(1).size() + >= 32 : true' + - message: digest must only contain hex characters (A-F, + a-f, 0-9) + rule: 'self.find(''(@.*:)'') != '''' ? self.find('':.*$'').matches('':[0-9A-Fa-f]*$'') + : true' + paths: + description: |- + paths specifies which directories from the image are mounted. + Each path is mounted as a separate subPath volumeMount into the agent's + skills directory. The last segment of each path becomes the mount name + (e.g., "/skills/prometheus" mounts as "prometheus"). + + Each path must be an absolute file path: starts with "/", no ".." + or "." segments, no double slashes, no trailing slash, and only + alphanumeric characters, hyphens, underscores, dots, and slashes. + + Maximum 50 items. + items: + maxLength: 512 + minLength: 2 + type: string + maxItems: 50 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + x-kubernetes-validations: + - message: each path must be 2-512 characters + rule: self.all(p, p.size() >= 2 && p.size() <= 512) + - message: each path must be absolute (start with '/') + rule: self.all(p, p.startsWith('/')) + - message: paths must not end with '/' + rule: self.all(p, !p.endsWith('/')) + - message: paths must not contain double slashes + rule: self.all(p, !p.contains('//')) + - message: paths must not contain '.' or '..' segments + rule: self.all(p, !p.contains('/../') && !p.endsWith('/..') + && !p.contains('/./') && !p.endsWith('/.')) + - message: paths may only contain alphanumeric characters, + '/', '_', '.', and '-' + rule: self.all(p, p.matches('^[a-zA-Z0-9/_.-]+$')) + required: + - image + - paths + type: object + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - image + x-kubernetes-list-type: map + type: object + type: object + analysisOutput: + description: |- + analysisOutput configures the analysis step's structured output. + The mode field controls which built-in properties are included + (Default: all; Minimal: only title). The schema field optionally + defines adapter-specific structured data injected as "components". + + When omitted, the analysis uses the full default schema with all + built-in properties and no custom components. + + Immutable: the output contract is fixed at creation. + minProperties: 1 + properties: + mode: + default: Default + description: |- + mode controls which built-in properties the analysis output schema + includes. Default includes all built-in properties (diagnosis, + proposal, summary, rbac, verification). Minimal includes only the + base structure (options array with title per option). Omit or set + to "Default" for standard remediation workflows. + enum: + - Default + - Minimal + type: string + schema: + description: |- + schema is a JSON Schema injected as a required "components" + property in each analysis output option. Use this to require + adapter-specific structured data beyond the base analysis schema. + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + x-kubernetes-validations: + - message: schema is required when mode is Minimal + rule: self.mode != 'Minimal' || has(self.schema) + execution: + description: |- + execution defines per-step configuration for the execution step. + Omit to skip execution (advisory/assisted patterns). + + Immutable: agent and per-step tools are fixed at creation. + minProperties: 1 + properties: + agent: + description: |- + agent is the name of the cluster-scoped Agent CR to use for this step. + Defaults to "default" when omitted. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS subdomain: lowercase alphanumeric + characters, hyphens, and dots' + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + tools: + description: |- + tools provides per-step tools that replace the shared spec.tools + for this step. Use this when different steps need different skills. + minProperties: 1 + properties: + mcpServers: + description: |- + mcpServers defines external MCP (Model Context Protocol) servers the + agent can connect to for additional tools and context. + items: + description: "MCPServerConfig defines the configuration + for an MCP (Model Context Protocol)\nserver that the agent + can connect to for additional tools and context.\nMCP + servers extend the agent's capabilities beyond its built-in + skills.\n\nExample — connecting to an OpenShift MCP server + with SA token auth:\n\n\tmcpServers:\n\t - name: openshift\n\t + \ url: https://mcp.openshift-lightspeed.svc:8443/sse\n\t + \ timeoutSeconds: 10\n\t headers:\n\t - name: + Authorization\n\t valueFrom:\n\t type: + ServiceAccountToken\n\nExample — connecting to an external + API with secret-based auth:\n\n\tmcpServers:\n\t - name: + pagerduty\n\t url: https://mcp-pagerduty.example.com/sse\n\t + \ headers:\n\t - name: X-API-Key\n\t valueFrom:\n\t + \ type: Secret\n\t secret:\n\t name: + pagerduty-api-key" + properties: + headers: + description: headers to send to the MCP server. Maximum + 20 items. + items: + description: |- + MCPHeader defines an HTTP header to send with every request to an + MCP server. Used for authentication and routing. + properties: + name: + description: |- + name of the header (e.g., "Authorization", "X-API-Key"). + Must be at least 1 character, containing only letters, digits, and hyphens. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: name must start with a letter and contain + only letters, digits, and hyphens + rule: self.matches('^[A-Za-z][A-Za-z0-9-]*$') + valueFrom: + description: valueFrom is the source of the header + value. + properties: + secret: + description: |- + secret references a Secret containing the header value. + Required when type is "Secret". + properties: + name: + description: name of the Secret. Must + be a valid RFC 1123 DNS subdomain. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS subdomain: + lowercase alphanumeric characters, + hyphens, and dots' + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + required: + - name + type: object + type: + description: |- + type specifies the source type for the header value. Allowed values: + - "Secret" — reads the value from a Kubernetes Secret (use for + API keys and tokens). Requires the secret field to be set. + - "ServiceAccountToken" — auto-injects a Kubernetes service account token + (for MCP servers that accept K8s auth). + - "Client" — the value is provided by the calling client at + runtime (e.g., forwarded from a user session). + enum: + - Secret + - ServiceAccountToken + - Client + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: secret is required when type is Secret, + and forbidden otherwise + rule: 'self.type == ''Secret'' ? has(self.secret) + : !has(self.secret)' + required: + - name + - valueFrom + type: object + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + name: + description: |- + name of the MCP server. Must start with a letter and contain only + lowercase alphanumeric characters and hyphens. Must be 1-253 characters. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: name must start with a lowercase letter and + contain only lowercase alphanumerics and hyphens + rule: self.matches('^[a-z][a-z0-9-]*$') + timeoutSeconds: + default: 5 + description: |- + timeoutSeconds is the per-request timeout for calls to this MCP server, + in seconds. Default is 5. + Valid range: 1-300. + format: int32 + maximum: 300 + minimum: 1 + type: integer + url: + description: |- + url of the MCP server (HTTP/HTTPS). Must be an HTTP or HTTPS URL, + maximum 2048 characters. + maxLength: 2048 + minLength: 1 + type: string + x-kubernetes-validations: + - message: url must be a valid HTTP or HTTPS URL + rule: isURL(self) && url(self).getScheme() in ['http', + 'https'] + required: + - name + - url + type: object + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + requiredSecrets: + description: |- + requiredSecrets declares Kubernetes Secrets that the sandbox pod + needs at runtime. The cluster admin creates the actual Secrets + in the same namespace as the Proposal. + items: + description: |- + SecretRequirement declares a Kubernetes Secret that the sandbox needs + at runtime. The Secret must exist in the operator namespace (where + sandbox pods run), not in the Proposal's namespace. + properties: + description: + description: |- + description explains what this secret is used for, helping the + cluster admin understand what credentials to provide. + maxLength: 1024 + minLength: 1 + type: string + mountAs: + description: mountAs specifies how the secret is exposed + in the sandbox pod. + properties: + envVar: + description: |- + envVar configures environment variable injection. + Required when type is "EnvVar". + properties: + name: + description: |- + name is the environment variable name (e.g., "GITHUB_TOKEN"). + Must be uppercase letters, digits, and underscores, starting + with a letter or underscore. + maxLength: 256 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid environment variable + name: uppercase letters, digits, and underscores, + starting with a letter or underscore' + rule: self.matches('^[A-Z_][A-Z0-9_]*$') + required: + - name + type: object + filePath: + description: |- + filePath configures file mount. + Required when type is "FilePath". + properties: + path: + description: |- + path is the absolute file path (e.g., "/etc/secrets/tls.crt"). + Must start with a forward slash. + maxLength: 512 + minLength: 2 + type: string + x-kubernetes-validations: + - message: path must be an absolute path starting + with '/' + rule: self.startsWith('/') + required: + - path + type: object + type: + description: |- + type specifies how the secret is exposed. Allowed values: "EnvVar", + "FilePath". + + When set to EnvVar, the secret value is injected as an environment + variable, and the 'envVar' field must be configured. + + When set to FilePath, the secret is mounted as a file, and the + 'filePath' field must be configured. + enum: + - EnvVar + - FilePath + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: envVar is required when type is EnvVar, and + forbidden otherwise + rule: 'self.type == ''EnvVar'' ? has(self.envVar) + : !has(self.envVar)' + - message: filePath is required when type is FilePath, + and forbidden otherwise + rule: 'self.type == ''FilePath'' ? has(self.filePath) + : !has(self.filePath)' + name: + description: |- + name of the Secret (must exist in the operator namespace). + Must be a valid RFC 1123 DNS subdomain. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS subdomain: lowercase + alphanumeric characters, hyphens, and dots' + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + required: + - mountAs + - name + type: object + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + skills: + description: |- + skills defines one or more OCI images containing skills to mount + in the agent's sandbox pod. The operator creates Kubernetes image + volumes (requires K8s 1.34+) and mounts them into the agent's + skills directory. Each image must be unique within the list. + items: + description: "SkillsSource defines an OCI image containing + skills and which paths\nwithin that image to mount. Skills + are mounted as Kubernetes image\nvolumes in the agent's + sandbox pod.\n\nEach path is mounted as a separate subPath + volumeMount, allowing\nselective composition of skills + from shared images.\n\nExample — mount specific skills + from the agentic-skills image:\n\n\tskills:\n\t - image: + registry.ci.openshift.org/ocp/5.0:agentic-skills\n\t paths:\n\t + \ - /skills/cluster-update/update-advisor" + properties: + image: + description: |- + image is the OCI image reference containing skills. + The operator mounts this as a Kubernetes image volume (requires K8s 1.34+). + Must be a valid OCI image pullspec: a domain, followed by a repository path, + ending with either a tag (:tag) or a digest (@algorithm:hex). + Must be 1-512 characters. + maxLength: 512 + minLength: 1 + type: string + x-kubernetes-validations: + - message: must start with a valid domain. valid domains + must be alphanumeric characters (lowercase and uppercase) + separated by the '.' character. + rule: self.matches('^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])((\\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]))+)?(:[0-9]+)?\\b') + - message: a valid name is required. valid names must + contain lowercase alphanumeric characters separated + only by the '.', '_', '__', '-' characters. + rule: self.find('(/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?((/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?)+)?)') + != '' + - message: must end with a digest or a tag + rule: self.find('(@.*:)') != '' || self.find(':.*$') + != '' + - message: tag must not be more than 127 characters + rule: 'self.find(''(@.*:)'') == '''' ? (self.find('':.*$'') + != '''' ? self.find('':.*$'').substring(1).size() + <= 127 : true) : true' + - message: tag is invalid. valid tags must begin with + a word character followed by word characters, '.', + or '-' + rule: 'self.find(''(@.*:)'') == '''' ? (self.find('':.*$'') + != '''' ? self.find('':.*$'').matches('':[\\w][\\w.-]*$'') + : true) : true' + - message: digest algorithm is not valid. valid algorithms + must start with an alpha character followed by alphanumeric + characters and may contain '-', '_', '+', and '.' + characters. + rule: 'self.find(''(@.*:)'') != '''' ? self.find(''(@.*:)'').matches(''(@[A-Za-z][A-Za-z0-9]*([_+.][A-Za-z][A-Za-z0-9]*)*[:])'') + : true' + - message: digest must be at least 32 characters + rule: 'self.find(''(@.*:)'') != '''' ? self.find('':.*$'').substring(1).size() + >= 32 : true' + - message: digest must only contain hex characters (A-F, + a-f, 0-9) + rule: 'self.find(''(@.*:)'') != '''' ? self.find('':.*$'').matches('':[0-9A-Fa-f]*$'') + : true' + paths: + description: |- + paths specifies which directories from the image are mounted. + Each path is mounted as a separate subPath volumeMount into the agent's + skills directory. The last segment of each path becomes the mount name + (e.g., "/skills/prometheus" mounts as "prometheus"). + + Each path must be an absolute file path: starts with "/", no ".." + or "." segments, no double slashes, no trailing slash, and only + alphanumeric characters, hyphens, underscores, dots, and slashes. + + Maximum 50 items. + items: + maxLength: 512 + minLength: 2 + type: string + maxItems: 50 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + x-kubernetes-validations: + - message: each path must be 2-512 characters + rule: self.all(p, p.size() >= 2 && p.size() <= 512) + - message: each path must be absolute (start with '/') + rule: self.all(p, p.startsWith('/')) + - message: paths must not end with '/' + rule: self.all(p, !p.endsWith('/')) + - message: paths must not contain double slashes + rule: self.all(p, !p.contains('//')) + - message: paths must not contain '.' or '..' segments + rule: self.all(p, !p.contains('/../') && !p.endsWith('/..') + && !p.contains('/./') && !p.endsWith('/.')) + - message: paths may only contain alphanumeric characters, + '/', '_', '.', and '-' + rule: self.all(p, p.matches('^[a-zA-Z0-9/_.-]+$')) + required: + - image + - paths + type: object + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - image + x-kubernetes-list-type: map + type: object + type: object + request: + description: |- + request is the user's original request, alert description, or a + description of what triggered this proposal. This text is passed to + the analysis agent as the primary input. + + Immutable: Proposals are run-to-completion (like Jobs). To change + the request, create a new Proposal. Use spec.revisionFeedback for + iterative feedback on an existing analysis. + maxLength: 32768 + minLength: 1 + type: string + x-kubernetes-validations: + - message: request is immutable after creation + rule: self == oldSelf + revisionFeedback: + description: |- + revisionFeedback is the user's free-text feedback requesting changes + to the analysis. Patching this field bumps metadata.generation, which + the operator detects (generation > observedGeneration) and triggers + re-analysis with the feedback appended to the original request. + + Mutable: this is the only mutable spec field. All other spec fields + are immutable via CEL rules, so generation changes signal revision. + maxLength: 32768 + minLength: 1 + type: string + targetNamespaces: + description: |- + targetNamespaces are the Kubernetes namespace(s) this proposal + operates on. Used for RBAC scoping and context to the analysis agent. + + When omitted, the proposal is not namespace-scoped — the analysis + agent determines the relevant namespaces from the request context. + Adapters (AlertManager, ACS) typically set this automatically from + the source event. + + Immutable: RBAC scoping is fixed at creation. Changing target + namespaces mid-flight would invalidate the analysis and any + granted execution RBAC. + items: + maxLength: 63 + minLength: 1 + type: string + maxItems: 50 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + x-kubernetes-validations: + - message: each namespace must be a valid DNS label + rule: self.all(ns, !format.dns1123Label().validate(ns).hasValue()) + tools: + description: |- + tools defines the default tools for all steps: skills images, + MCP servers, and required secrets. Per-step tools + (analysis.tools, execution.tools, verification.tools) replace + this default for individual steps. + + Immutable: the skills and secrets available to the agent are + fixed at creation. Changing tools mid-flight could violate the + assumptions of an in-progress analysis or execution. + minProperties: 1 + properties: + mcpServers: + description: |- + mcpServers defines external MCP (Model Context Protocol) servers the + agent can connect to for additional tools and context. + items: + description: "MCPServerConfig defines the configuration for + an MCP (Model Context Protocol)\nserver that the agent can + connect to for additional tools and context.\nMCP servers + extend the agent's capabilities beyond its built-in skills.\n\nExample + — connecting to an OpenShift MCP server with SA token auth:\n\n\tmcpServers:\n\t + \ - name: openshift\n\t url: https://mcp.openshift-lightspeed.svc:8443/sse\n\t + \ timeoutSeconds: 10\n\t headers:\n\t - name: Authorization\n\t + \ valueFrom:\n\t type: ServiceAccountToken\n\nExample + — connecting to an external API with secret-based auth:\n\n\tmcpServers:\n\t + \ - name: pagerduty\n\t url: https://mcp-pagerduty.example.com/sse\n\t + \ headers:\n\t - name: X-API-Key\n\t valueFrom:\n\t + \ type: Secret\n\t secret:\n\t name: + pagerduty-api-key" + properties: + headers: + description: headers to send to the MCP server. Maximum + 20 items. + items: + description: |- + MCPHeader defines an HTTP header to send with every request to an + MCP server. Used for authentication and routing. + properties: + name: + description: |- + name of the header (e.g., "Authorization", "X-API-Key"). + Must be at least 1 character, containing only letters, digits, and hyphens. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: name must start with a letter and contain + only letters, digits, and hyphens + rule: self.matches('^[A-Za-z][A-Za-z0-9-]*$') + valueFrom: + description: valueFrom is the source of the header + value. + properties: + secret: + description: |- + secret references a Secret containing the header value. + Required when type is "Secret". + properties: + name: + description: name of the Secret. Must be a + valid RFC 1123 DNS subdomain. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS subdomain: + lowercase alphanumeric characters, hyphens, + and dots' + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + required: + - name + type: object + type: + description: |- + type specifies the source type for the header value. Allowed values: + - "Secret" — reads the value from a Kubernetes Secret (use for + API keys and tokens). Requires the secret field to be set. + - "ServiceAccountToken" — auto-injects a Kubernetes service account token + (for MCP servers that accept K8s auth). + - "Client" — the value is provided by the calling client at + runtime (e.g., forwarded from a user session). + enum: + - Secret + - ServiceAccountToken + - Client + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: secret is required when type is Secret, + and forbidden otherwise + rule: 'self.type == ''Secret'' ? has(self.secret) + : !has(self.secret)' + required: + - name + - valueFrom + type: object + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + name: + description: |- + name of the MCP server. Must start with a letter and contain only + lowercase alphanumeric characters and hyphens. Must be 1-253 characters. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: name must start with a lowercase letter and contain + only lowercase alphanumerics and hyphens + rule: self.matches('^[a-z][a-z0-9-]*$') + timeoutSeconds: + default: 5 + description: |- + timeoutSeconds is the per-request timeout for calls to this MCP server, + in seconds. Default is 5. + Valid range: 1-300. + format: int32 + maximum: 300 + minimum: 1 + type: integer + url: + description: |- + url of the MCP server (HTTP/HTTPS). Must be an HTTP or HTTPS URL, + maximum 2048 characters. + maxLength: 2048 + minLength: 1 + type: string + x-kubernetes-validations: + - message: url must be a valid HTTP or HTTPS URL + rule: isURL(self) && url(self).getScheme() in ['http', + 'https'] + required: + - name + - url + type: object + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + requiredSecrets: + description: |- + requiredSecrets declares Kubernetes Secrets that the sandbox pod + needs at runtime. The cluster admin creates the actual Secrets + in the same namespace as the Proposal. + items: + description: |- + SecretRequirement declares a Kubernetes Secret that the sandbox needs + at runtime. The Secret must exist in the operator namespace (where + sandbox pods run), not in the Proposal's namespace. + properties: + description: + description: |- + description explains what this secret is used for, helping the + cluster admin understand what credentials to provide. + maxLength: 1024 + minLength: 1 + type: string + mountAs: + description: mountAs specifies how the secret is exposed + in the sandbox pod. + properties: + envVar: + description: |- + envVar configures environment variable injection. + Required when type is "EnvVar". + properties: + name: + description: |- + name is the environment variable name (e.g., "GITHUB_TOKEN"). + Must be uppercase letters, digits, and underscores, starting + with a letter or underscore. + maxLength: 256 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid environment variable + name: uppercase letters, digits, and underscores, + starting with a letter or underscore' + rule: self.matches('^[A-Z_][A-Z0-9_]*$') + required: + - name + type: object + filePath: + description: |- + filePath configures file mount. + Required when type is "FilePath". + properties: + path: + description: |- + path is the absolute file path (e.g., "/etc/secrets/tls.crt"). + Must start with a forward slash. + maxLength: 512 + minLength: 2 + type: string + x-kubernetes-validations: + - message: path must be an absolute path starting + with '/' + rule: self.startsWith('/') + required: + - path + type: object + type: + description: |- + type specifies how the secret is exposed. Allowed values: "EnvVar", + "FilePath". + + When set to EnvVar, the secret value is injected as an environment + variable, and the 'envVar' field must be configured. + + When set to FilePath, the secret is mounted as a file, and the + 'filePath' field must be configured. + enum: + - EnvVar + - FilePath + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: envVar is required when type is EnvVar, and forbidden + otherwise + rule: 'self.type == ''EnvVar'' ? has(self.envVar) : !has(self.envVar)' + - message: filePath is required when type is FilePath, and + forbidden otherwise + rule: 'self.type == ''FilePath'' ? has(self.filePath) + : !has(self.filePath)' + name: + description: |- + name of the Secret (must exist in the operator namespace). + Must be a valid RFC 1123 DNS subdomain. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS subdomain: lowercase alphanumeric + characters, hyphens, and dots' + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + required: + - mountAs + - name + type: object + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + skills: + description: |- + skills defines one or more OCI images containing skills to mount + in the agent's sandbox pod. The operator creates Kubernetes image + volumes (requires K8s 1.34+) and mounts them into the agent's + skills directory. Each image must be unique within the list. + items: + description: "SkillsSource defines an OCI image containing skills + and which paths\nwithin that image to mount. Skills are mounted + as Kubernetes image\nvolumes in the agent's sandbox pod.\n\nEach + path is mounted as a separate subPath volumeMount, allowing\nselective + composition of skills from shared images.\n\nExample — mount + specific skills from the agentic-skills image:\n\n\tskills:\n\t + \ - image: registry.ci.openshift.org/ocp/5.0:agentic-skills\n\t + \ paths:\n\t - /skills/cluster-update/update-advisor" + properties: + image: + description: |- + image is the OCI image reference containing skills. + The operator mounts this as a Kubernetes image volume (requires K8s 1.34+). + Must be a valid OCI image pullspec: a domain, followed by a repository path, + ending with either a tag (:tag) or a digest (@algorithm:hex). + Must be 1-512 characters. + maxLength: 512 + minLength: 1 + type: string + x-kubernetes-validations: + - message: must start with a valid domain. valid domains + must be alphanumeric characters (lowercase and uppercase) + separated by the '.' character. + rule: self.matches('^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])((\\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]))+)?(:[0-9]+)?\\b') + - message: a valid name is required. valid names must contain + lowercase alphanumeric characters separated only by + the '.', '_', '__', '-' characters. + rule: self.find('(/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?((/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?)+)?)') + != '' + - message: must end with a digest or a tag + rule: self.find('(@.*:)') != '' || self.find(':.*$') != + '' + - message: tag must not be more than 127 characters + rule: 'self.find(''(@.*:)'') == '''' ? (self.find('':.*$'') + != '''' ? self.find('':.*$'').substring(1).size() <= + 127 : true) : true' + - message: tag is invalid. valid tags must begin with a + word character followed by word characters, '.', or + '-' + rule: 'self.find(''(@.*:)'') == '''' ? (self.find('':.*$'') + != '''' ? self.find('':.*$'').matches('':[\\w][\\w.-]*$'') + : true) : true' + - message: digest algorithm is not valid. valid algorithms + must start with an alpha character followed by alphanumeric + characters and may contain '-', '_', '+', and '.' characters. + rule: 'self.find(''(@.*:)'') != '''' ? self.find(''(@.*:)'').matches(''(@[A-Za-z][A-Za-z0-9]*([_+.][A-Za-z][A-Za-z0-9]*)*[:])'') + : true' + - message: digest must be at least 32 characters + rule: 'self.find(''(@.*:)'') != '''' ? self.find('':.*$'').substring(1).size() + >= 32 : true' + - message: digest must only contain hex characters (A-F, + a-f, 0-9) + rule: 'self.find(''(@.*:)'') != '''' ? self.find('':.*$'').matches('':[0-9A-Fa-f]*$'') + : true' + paths: + description: |- + paths specifies which directories from the image are mounted. + Each path is mounted as a separate subPath volumeMount into the agent's + skills directory. The last segment of each path becomes the mount name + (e.g., "/skills/prometheus" mounts as "prometheus"). + + Each path must be an absolute file path: starts with "/", no ".." + or "." segments, no double slashes, no trailing slash, and only + alphanumeric characters, hyphens, underscores, dots, and slashes. + + Maximum 50 items. + items: + maxLength: 512 + minLength: 2 + type: string + maxItems: 50 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + x-kubernetes-validations: + - message: each path must be 2-512 characters + rule: self.all(p, p.size() >= 2 && p.size() <= 512) + - message: each path must be absolute (start with '/') + rule: self.all(p, p.startsWith('/')) + - message: paths must not end with '/' + rule: self.all(p, !p.endsWith('/')) + - message: paths must not contain double slashes + rule: self.all(p, !p.contains('//')) + - message: paths must not contain '.' or '..' segments + rule: self.all(p, !p.contains('/../') && !p.endsWith('/..') + && !p.contains('/./') && !p.endsWith('/.')) + - message: paths may only contain alphanumeric characters, + '/', '_', '.', and '-' + rule: self.all(p, p.matches('^[a-zA-Z0-9/_.-]+$')) + required: + - image + - paths + type: object + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - image + x-kubernetes-list-type: map + type: object + verification: + description: |- + verification defines per-step configuration for the verification step. + Omit to skip verification. + + Immutable: agent and per-step tools are fixed at creation. + minProperties: 1 + properties: + agent: + description: |- + agent is the name of the cluster-scoped Agent CR to use for this step. + Defaults to "default" when omitted. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS subdomain: lowercase alphanumeric + characters, hyphens, and dots' + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + tools: + description: |- + tools provides per-step tools that replace the shared spec.tools + for this step. Use this when different steps need different skills. + minProperties: 1 + properties: + mcpServers: + description: |- + mcpServers defines external MCP (Model Context Protocol) servers the + agent can connect to for additional tools and context. + items: + description: "MCPServerConfig defines the configuration + for an MCP (Model Context Protocol)\nserver that the agent + can connect to for additional tools and context.\nMCP + servers extend the agent's capabilities beyond its built-in + skills.\n\nExample — connecting to an OpenShift MCP server + with SA token auth:\n\n\tmcpServers:\n\t - name: openshift\n\t + \ url: https://mcp.openshift-lightspeed.svc:8443/sse\n\t + \ timeoutSeconds: 10\n\t headers:\n\t - name: + Authorization\n\t valueFrom:\n\t type: + ServiceAccountToken\n\nExample — connecting to an external + API with secret-based auth:\n\n\tmcpServers:\n\t - name: + pagerduty\n\t url: https://mcp-pagerduty.example.com/sse\n\t + \ headers:\n\t - name: X-API-Key\n\t valueFrom:\n\t + \ type: Secret\n\t secret:\n\t name: + pagerduty-api-key" + properties: + headers: + description: headers to send to the MCP server. Maximum + 20 items. + items: + description: |- + MCPHeader defines an HTTP header to send with every request to an + MCP server. Used for authentication and routing. + properties: + name: + description: |- + name of the header (e.g., "Authorization", "X-API-Key"). + Must be at least 1 character, containing only letters, digits, and hyphens. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: name must start with a letter and contain + only letters, digits, and hyphens + rule: self.matches('^[A-Za-z][A-Za-z0-9-]*$') + valueFrom: + description: valueFrom is the source of the header + value. + properties: + secret: + description: |- + secret references a Secret containing the header value. + Required when type is "Secret". + properties: + name: + description: name of the Secret. Must + be a valid RFC 1123 DNS subdomain. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS subdomain: + lowercase alphanumeric characters, + hyphens, and dots' + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + required: + - name + type: object + type: + description: |- + type specifies the source type for the header value. Allowed values: + - "Secret" — reads the value from a Kubernetes Secret (use for + API keys and tokens). Requires the secret field to be set. + - "ServiceAccountToken" — auto-injects a Kubernetes service account token + (for MCP servers that accept K8s auth). + - "Client" — the value is provided by the calling client at + runtime (e.g., forwarded from a user session). + enum: + - Secret + - ServiceAccountToken + - Client + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: secret is required when type is Secret, + and forbidden otherwise + rule: 'self.type == ''Secret'' ? has(self.secret) + : !has(self.secret)' + required: + - name + - valueFrom + type: object + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + name: + description: |- + name of the MCP server. Must start with a letter and contain only + lowercase alphanumeric characters and hyphens. Must be 1-253 characters. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: name must start with a lowercase letter and + contain only lowercase alphanumerics and hyphens + rule: self.matches('^[a-z][a-z0-9-]*$') + timeoutSeconds: + default: 5 + description: |- + timeoutSeconds is the per-request timeout for calls to this MCP server, + in seconds. Default is 5. + Valid range: 1-300. + format: int32 + maximum: 300 + minimum: 1 + type: integer + url: + description: |- + url of the MCP server (HTTP/HTTPS). Must be an HTTP or HTTPS URL, + maximum 2048 characters. + maxLength: 2048 + minLength: 1 + type: string + x-kubernetes-validations: + - message: url must be a valid HTTP or HTTPS URL + rule: isURL(self) && url(self).getScheme() in ['http', + 'https'] + required: + - name + - url + type: object + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + requiredSecrets: + description: |- + requiredSecrets declares Kubernetes Secrets that the sandbox pod + needs at runtime. The cluster admin creates the actual Secrets + in the same namespace as the Proposal. + items: + description: |- + SecretRequirement declares a Kubernetes Secret that the sandbox needs + at runtime. The Secret must exist in the operator namespace (where + sandbox pods run), not in the Proposal's namespace. + properties: + description: + description: |- + description explains what this secret is used for, helping the + cluster admin understand what credentials to provide. + maxLength: 1024 + minLength: 1 + type: string + mountAs: + description: mountAs specifies how the secret is exposed + in the sandbox pod. + properties: + envVar: + description: |- + envVar configures environment variable injection. + Required when type is "EnvVar". + properties: + name: + description: |- + name is the environment variable name (e.g., "GITHUB_TOKEN"). + Must be uppercase letters, digits, and underscores, starting + with a letter or underscore. + maxLength: 256 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid environment variable + name: uppercase letters, digits, and underscores, + starting with a letter or underscore' + rule: self.matches('^[A-Z_][A-Z0-9_]*$') + required: + - name + type: object + filePath: + description: |- + filePath configures file mount. + Required when type is "FilePath". + properties: + path: + description: |- + path is the absolute file path (e.g., "/etc/secrets/tls.crt"). + Must start with a forward slash. + maxLength: 512 + minLength: 2 + type: string + x-kubernetes-validations: + - message: path must be an absolute path starting + with '/' + rule: self.startsWith('/') + required: + - path + type: object + type: + description: |- + type specifies how the secret is exposed. Allowed values: "EnvVar", + "FilePath". + + When set to EnvVar, the secret value is injected as an environment + variable, and the 'envVar' field must be configured. + + When set to FilePath, the secret is mounted as a file, and the + 'filePath' field must be configured. + enum: + - EnvVar + - FilePath + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: envVar is required when type is EnvVar, and + forbidden otherwise + rule: 'self.type == ''EnvVar'' ? has(self.envVar) + : !has(self.envVar)' + - message: filePath is required when type is FilePath, + and forbidden otherwise + rule: 'self.type == ''FilePath'' ? has(self.filePath) + : !has(self.filePath)' + name: + description: |- + name of the Secret (must exist in the operator namespace). + Must be a valid RFC 1123 DNS subdomain. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS subdomain: lowercase + alphanumeric characters, hyphens, and dots' + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + required: + - mountAs + - name + type: object + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + skills: + description: |- + skills defines one or more OCI images containing skills to mount + in the agent's sandbox pod. The operator creates Kubernetes image + volumes (requires K8s 1.34+) and mounts them into the agent's + skills directory. Each image must be unique within the list. + items: + description: "SkillsSource defines an OCI image containing + skills and which paths\nwithin that image to mount. Skills + are mounted as Kubernetes image\nvolumes in the agent's + sandbox pod.\n\nEach path is mounted as a separate subPath + volumeMount, allowing\nselective composition of skills + from shared images.\n\nExample — mount specific skills + from the agentic-skills image:\n\n\tskills:\n\t - image: + registry.ci.openshift.org/ocp/5.0:agentic-skills\n\t paths:\n\t + \ - /skills/cluster-update/update-advisor" + properties: + image: + description: |- + image is the OCI image reference containing skills. + The operator mounts this as a Kubernetes image volume (requires K8s 1.34+). + Must be a valid OCI image pullspec: a domain, followed by a repository path, + ending with either a tag (:tag) or a digest (@algorithm:hex). + Must be 1-512 characters. + maxLength: 512 + minLength: 1 + type: string + x-kubernetes-validations: + - message: must start with a valid domain. valid domains + must be alphanumeric characters (lowercase and uppercase) + separated by the '.' character. + rule: self.matches('^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])((\\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]))+)?(:[0-9]+)?\\b') + - message: a valid name is required. valid names must + contain lowercase alphanumeric characters separated + only by the '.', '_', '__', '-' characters. + rule: self.find('(/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?((/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?)+)?)') + != '' + - message: must end with a digest or a tag + rule: self.find('(@.*:)') != '' || self.find(':.*$') + != '' + - message: tag must not be more than 127 characters + rule: 'self.find(''(@.*:)'') == '''' ? (self.find('':.*$'') + != '''' ? self.find('':.*$'').substring(1).size() + <= 127 : true) : true' + - message: tag is invalid. valid tags must begin with + a word character followed by word characters, '.', + or '-' + rule: 'self.find(''(@.*:)'') == '''' ? (self.find('':.*$'') + != '''' ? self.find('':.*$'').matches('':[\\w][\\w.-]*$'') + : true) : true' + - message: digest algorithm is not valid. valid algorithms + must start with an alpha character followed by alphanumeric + characters and may contain '-', '_', '+', and '.' + characters. + rule: 'self.find(''(@.*:)'') != '''' ? self.find(''(@.*:)'').matches(''(@[A-Za-z][A-Za-z0-9]*([_+.][A-Za-z][A-Za-z0-9]*)*[:])'') + : true' + - message: digest must be at least 32 characters + rule: 'self.find(''(@.*:)'') != '''' ? self.find('':.*$'').substring(1).size() + >= 32 : true' + - message: digest must only contain hex characters (A-F, + a-f, 0-9) + rule: 'self.find(''(@.*:)'') != '''' ? self.find('':.*$'').matches('':[0-9A-Fa-f]*$'') + : true' + paths: + description: |- + paths specifies which directories from the image are mounted. + Each path is mounted as a separate subPath volumeMount into the agent's + skills directory. The last segment of each path becomes the mount name + (e.g., "/skills/prometheus" mounts as "prometheus"). + + Each path must be an absolute file path: starts with "/", no ".." + or "." segments, no double slashes, no trailing slash, and only + alphanumeric characters, hyphens, underscores, dots, and slashes. + + Maximum 50 items. + items: + maxLength: 512 + minLength: 2 + type: string + maxItems: 50 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + x-kubernetes-validations: + - message: each path must be 2-512 characters + rule: self.all(p, p.size() >= 2 && p.size() <= 512) + - message: each path must be absolute (start with '/') + rule: self.all(p, p.startsWith('/')) + - message: paths must not end with '/' + rule: self.all(p, !p.endsWith('/')) + - message: paths must not contain double slashes + rule: self.all(p, !p.contains('//')) + - message: paths must not contain '.' or '..' segments + rule: self.all(p, !p.contains('/../') && !p.endsWith('/..') + && !p.contains('/./') && !p.endsWith('/.')) + - message: paths may only contain alphanumeric characters, + '/', '_', '.', and '-' + rule: self.all(p, p.matches('^[a-zA-Z0-9/_.-]+$')) + required: + - image + - paths + type: object + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - image + x-kubernetes-list-type: map + type: object + type: object + required: + - analysis + - request + type: object + x-kubernetes-validations: + - message: analysis must be provided + rule: has(self.analysis) + - message: targetNamespaces is immutable once set + rule: '!has(oldSelf.targetNamespaces) || (has(self.targetNamespaces) + && self.targetNamespaces == oldSelf.targetNamespaces)' + - message: analysisOutput is immutable once set + rule: '!has(oldSelf.analysisOutput) || (has(self.analysisOutput) && + self.analysisOutput == oldSelf.analysisOutput)' + - message: analysisOutput mode Minimal is only allowed for analysis-only + proposals (no execution or verification steps) + rule: '!has(self.analysisOutput) || self.analysisOutput.mode != ''Minimal'' + || (!has(self.execution) && !has(self.verification))' + - message: tools is immutable once set + rule: '!has(oldSelf.tools) || (has(self.tools) && self.tools == oldSelf.tools)' + - message: analysis is immutable once set + rule: '!has(oldSelf.analysis) || (has(self.analysis) && self.analysis + == oldSelf.analysis)' + - message: execution is immutable once set + rule: '!has(oldSelf.execution) || (has(self.execution) && self.execution + == oldSelf.execution)' + - message: verification is immutable once set + rule: '!has(oldSelf.verification) || (has(self.verification) && self.verification + == oldSelf.verification)' + status: + description: status defines the observed state of Proposal. + minProperties: 1 + properties: + conditions: + description: |- + conditions represent the latest available observations using the + standard Kubernetes condition pattern. Condition types include: + Analyzed, Approved, Executed, Verified, and Escalated. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + steps: + description: |- + steps contains the per-step observed state (analysis, execution, + verification). Each step independently tracks its timing, sandbox + info, and references to result CRs. + minProperties: 1 + properties: + analysis: + description: analysis is the observed state of the analysis step. + minProperties: 1 + properties: + conditions: + description: conditions for this step. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + results: + description: |- + results references AnalysisResult CRs, newest last. + Each entry corresponds to one analysis attempt. + items: + description: |- + StepResultRef is a lightweight reference to a result CR with an inline + success field for quick scanning without fetching the CR. + properties: + name: + description: name is the name of the result CR. + maxLength: 253 + minLength: 1 + type: string + outcome: + description: |- + outcome indicates the result of this step attempt. + Must be one of: Succeeded, Failed. + enum: + - Succeeded + - Failed + type: string + required: + - name + - outcome + type: object + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + sandbox: + description: sandbox tracks the sandbox used. + properties: + claimName: + description: |- + claimName is the name of the SandboxClaim resource that owns the + sandbox pod. Maximum 253 characters. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + namespace is the namespace where the SandboxClaim and its pod live. + Must be a valid RFC 1123 DNS label. + maxLength: 63 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS label: lowercase alphanumeric + characters and hyphens, starting with an alphabetic + character and ending with an alphanumeric character' + rule: '!format.dns1123Label().validate(self).hasValue()' + required: + - claimName + - namespace + type: object + type: object + escalation: + description: escalation is the observed state of the escalation + step. + minProperties: 1 + properties: + conditions: + description: conditions for this step. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + results: + description: results references EscalationResult CRs, newest + last. + items: + description: |- + StepResultRef is a lightweight reference to a result CR with an inline + success field for quick scanning without fetching the CR. + properties: + name: + description: name is the name of the result CR. + maxLength: 253 + minLength: 1 + type: string + outcome: + description: |- + outcome indicates the result of this step attempt. + Must be one of: Succeeded, Failed. + enum: + - Succeeded + - Failed + type: string + required: + - name + - outcome + type: object + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + sandbox: + description: sandbox tracks the sandbox used. + properties: + claimName: + description: |- + claimName is the name of the SandboxClaim resource that owns the + sandbox pod. Maximum 253 characters. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + namespace is the namespace where the SandboxClaim and its pod live. + Must be a valid RFC 1123 DNS label. + maxLength: 63 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS label: lowercase alphanumeric + characters and hyphens, starting with an alphabetic + character and ending with an alphanumeric character' + rule: '!format.dns1123Label().validate(self).hasValue()' + required: + - claimName + - namespace + type: object + type: object + execution: + description: execution is the observed state of the execution + step. + minProperties: 1 + properties: + conditions: + description: conditions for this step. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + results: + description: |- + results references ExecutionResult CRs, newest last. + Each entry corresponds to one execution attempt (including retries). + items: + description: |- + StepResultRef is a lightweight reference to a result CR with an inline + success field for quick scanning without fetching the CR. + properties: + name: + description: name is the name of the result CR. + maxLength: 253 + minLength: 1 + type: string + outcome: + description: |- + outcome indicates the result of this step attempt. + Must be one of: Succeeded, Failed. + enum: + - Succeeded + - Failed + type: string + required: + - name + - outcome + type: object + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + retryCount: + description: |- + retryCount tracks how many times execution+verification has been + retried for the current analysis option. Reset when a new analysis + is run (initial or revision). The operator increments this on each + objective verification failure before retrying execution. + format: int32 + minimum: 0 + type: integer + sandbox: + description: sandbox tracks the sandbox used. + properties: + claimName: + description: |- + claimName is the name of the SandboxClaim resource that owns the + sandbox pod. Maximum 253 characters. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + namespace is the namespace where the SandboxClaim and its pod live. + Must be a valid RFC 1123 DNS label. + maxLength: 63 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS label: lowercase alphanumeric + characters and hyphens, starting with an alphabetic + character and ending with an alphanumeric character' + rule: '!format.dns1123Label().validate(self).hasValue()' + required: + - claimName + - namespace + type: object + type: object + verification: + description: verification is the observed state of the verification + step. + minProperties: 1 + properties: + conditions: + description: conditions for this step. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + results: + description: |- + results references VerificationResult CRs, newest last. + Each entry corresponds to one verification attempt (including retries). + items: + description: |- + StepResultRef is a lightweight reference to a result CR with an inline + success field for quick scanning without fetching the CR. + properties: + name: + description: name is the name of the result CR. + maxLength: 253 + minLength: 1 + type: string + outcome: + description: |- + outcome indicates the result of this step attempt. + Must be one of: Succeeded, Failed. + enum: + - Succeeded + - Failed + type: string + required: + - name + - outcome + type: object + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + sandbox: + description: sandbox tracks the sandbox used. + properties: + claimName: + description: |- + claimName is the name of the SandboxClaim resource that owns the + sandbox pod. Maximum 253 characters. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + namespace is the namespace where the SandboxClaim and its pod live. + Must be a valid RFC 1123 DNS label. + maxLength: 63 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS label: lowercase alphanumeric + characters and hyphens, starting with an alphabetic + character and ending with an alphanumeric character' + rule: '!format.dns1123Label().validate(self).hasValue()' + required: + - claimName + - namespace + type: object + type: object + type: object + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/bundle/manifests/agentic.openshift.io_verificationresults.yaml b/bundle/manifests/agentic.openshift.io_verificationresults.yaml new file mode 100644 index 000000000..dc0c1b25c --- /dev/null +++ b/bundle/manifests/agentic.openshift.io_verificationresults.yaml @@ -0,0 +1,241 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + creationTimestamp: null + name: verificationresults.agentic.openshift.io +spec: + group: agentic.openshift.io + names: + kind: VerificationResult + listKind: VerificationResultList + plural: verificationresults + singular: verificationresult + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.proposalName + name: Proposal + type: string + - jsonPath: .spec.retryIndex + name: Retry + type: integer + - jsonPath: .status.conditions[?(@.type=="Completed")].reason + name: Outcome + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + VerificationResult records the output of a single verification step + execution. Created by the operator after the verification agent + completes. Owned by the parent Proposal for garbage collection. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec contains the immutable identity fields for this result. + properties: + proposalName: + description: proposalName is the name of the parent Proposal in the + same namespace. + maxLength: 253 + minLength: 1 + type: string + retryIndex: + description: retryIndex is the 0-based retry index within the current + analysis. + format: int32 + maximum: 2 + minimum: 0 + type: integer + required: + - proposalName + - retryIndex + type: object + x-kubernetes-validations: + - message: spec is immutable + rule: self == oldSelf + status: + description: status contains result data and conditions. + minProperties: 1 + properties: + checks: + description: checks contains individual verification check results. + items: + description: |- + VerifyCheck is a single verification check result from the verification + agent. Each check corresponds to a VerificationStep from the analysis + agent's verification plan. + properties: + name: + description: |- + name is the check identifier, matching the VerificationStep name. + Maximum 253 characters. + maxLength: 253 + minLength: 1 + type: string + result: + description: |- + result indicates whether the check's observed value matches + the expected value. Must be one of: Passed, Failed. + enum: + - Passed + - Failed + type: string + source: + description: |- + source is what performed the check (e.g., "oc", "promql", "curl"). + Maximum 256 characters. + maxLength: 256 + minLength: 1 + type: string + value: + description: |- + value is the actual observed value (e.g., "Running", "3 replicas"). + Maximum 4096 characters. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - result + - source + - value + type: object + maxItems: 50 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + conditions: + description: conditions track the lifecycle of this result. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + failureReason: + description: failureReason is populated when the step failed due to + a system error. + maxLength: 8192 + minLength: 1 + type: string + sandbox: + description: sandbox tracks the sandbox pod used for this verification. + properties: + claimName: + description: |- + claimName is the name of the SandboxClaim resource that owns the + sandbox pod. Maximum 253 characters. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + namespace is the namespace where the SandboxClaim and its pod live. + Must be a valid RFC 1123 DNS label. + maxLength: 63 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS label: lowercase alphanumeric + characters and hyphens, starting with an alphabetic character + and ending with an alphanumeric character' + rule: '!format.dns1123Label().validate(self).hasValue()' + required: + - claimName + - namespace + type: object + summary: + description: summary is a Markdown-formatted verification summary. + maxLength: 32768 + minLength: 1 + type: string + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/bundle/manifests/lightspeed-operator.clusterserviceversion.yaml b/bundle/manifests/lightspeed-operator.clusterserviceversion.yaml index a43fe49fa..8563bd975 100644 --- a/bundle/manifests/lightspeed-operator.clusterserviceversion.yaml +++ b/bundle/manifests/lightspeed-operator.clusterserviceversion.yaml @@ -4,6 +4,154 @@ metadata: annotations: alm-examples: |- [ + { + "apiVersion": "agentic.openshift.io/v1alpha1", + "kind": "Agent", + "metadata": { + "name": "default" + }, + "spec": { + "llmProvider": { + "name": "vertex-ai" + }, + "maxTurns": 200, + "model": "claude-opus-4-6" + } + }, + { + "apiVersion": "agentic.openshift.io/v1alpha1", + "kind": "AnalysisResult", + "metadata": { + "name": "fix-crashloop", + "namespace": "openshift-lightspeed" + }, + "spec": { + "proposalName": "fix-crashloop" + } + }, + { + "apiVersion": "agentic.openshift.io/v1alpha1", + "kind": "ApprovalPolicy", + "metadata": { + "name": "cluster" + }, + "spec": { + "maxAttempts": 3, + "maxConcurrentProposals": 5, + "stages": [ + { + "approval": "Manual", + "name": "Analysis" + }, + { + "approval": "Manual", + "name": "Execution" + }, + { + "approval": "Manual", + "name": "Verification" + } + ] + } + }, + { + "apiVersion": "agentic.openshift.io/v1alpha1", + "kind": "EscalationResult", + "metadata": { + "name": "fix-crashloop", + "namespace": "openshift-lightspeed" + }, + "spec": { + "proposalName": "fix-crashloop" + } + }, + { + "apiVersion": "agentic.openshift.io/v1alpha1", + "kind": "ExecutionResult", + "metadata": { + "name": "fix-crashloop-0", + "namespace": "openshift-lightspeed" + }, + "spec": { + "proposalName": "fix-crashloop", + "retryIndex": 0 + } + }, + { + "apiVersion": "agentic.openshift.io/v1alpha1", + "kind": "LLMProvider", + "metadata": { + "name": "vertex-ai" + }, + "spec": { + "googleCloudVertex": { + "credentialsSecret": { + "name": "llm-credentials" + }, + "modelProvider": "Anthropic", + "projectID": "my-gcp-project", + "region": "us-central1" + }, + "type": "GoogleCloudVertex" + } + }, + { + "apiVersion": "agentic.openshift.io/v1alpha1", + "kind": "Proposal", + "metadata": { + "name": "fix-crashloop", + "namespace": "openshift-lightspeed" + }, + "spec": { + "analysis": { + "agent": "smart" + }, + "execution": { + "agent": "default" + }, + "request": "Pod api-server-xyz is crash looping in production namespace", + "targetNamespaces": [ + "production" + ], + "verification": { + "agent": "fast" + } + } + }, + { + "apiVersion": "agentic.openshift.io/v1alpha1", + "kind": "ProposalApproval", + "metadata": { + "name": "fix-crashloop", + "namespace": "openshift-lightspeed" + }, + "spec": { + "stages": [ + { + "analysis": {}, + "type": "Analysis" + }, + { + "execution": { + "option": 0 + }, + "type": "Execution" + } + ] + } + }, + { + "apiVersion": "agentic.openshift.io/v1alpha1", + "kind": "VerificationResult", + "metadata": { + "name": "fix-crashloop-0", + "namespace": "openshift-lightspeed" + }, + "spec": { + "proposalName": "fix-crashloop", + "retryIndex": 0 + } + }, { "apiVersion": "ols.openshift.io/v1alpha1", "kind": "OLSConfig", @@ -38,7 +186,7 @@ metadata: ] capabilities: Seamless Upgrades console.openshift.io/operator-monitoring-default: "true" - createdAt: "2026-05-20T12:27:36Z" + createdAt: "2026-06-04T08:15:04Z" features.operators.openshift.io/cnf: "false" features.operators.openshift.io/cni: "false" features.operators.openshift.io/csi: "false" @@ -61,6 +209,24 @@ spec: apiservicedefinitions: {} customresourcedefinitions: owned: + - kind: Agent + name: agents.agentic.openshift.io + version: v1alpha1 + - kind: AnalysisResult + name: analysisresults.agentic.openshift.io + version: v1alpha1 + - kind: ApprovalPolicy + name: approvalpolicies.agentic.openshift.io + version: v1alpha1 + - kind: EscalationResult + name: escalationresults.agentic.openshift.io + version: v1alpha1 + - kind: ExecutionResult + name: executionresults.agentic.openshift.io + version: v1alpha1 + - kind: LLMProvider + name: llmproviders.agentic.openshift.io + version: v1alpha1 - description: Red Hat OpenShift Lightspeed instance. OLSConfig is the Schema for the olsconfigs API displayName: OLSConfig kind: OLSConfig @@ -458,6 +624,15 @@ spec: displayName: Overall Status path: overallStatus version: v1alpha1 + - kind: ProposalApproval + name: proposalapprovals.agentic.openshift.io + version: v1alpha1 + - kind: Proposal + name: proposals.agentic.openshift.io + version: v1alpha1 + - kind: VerificationResult + name: verificationresults.agentic.openshift.io + version: v1alpha1 description: |- OpenShift Lightspeed Operator provides generative AI-based virtual assistant which integrates into the OpenShift web console. OpenShift Lightspeed can answer natural language questions related to OpenShift Container Platform. diff --git a/config/crd/bases/agentic.openshift.io_agents.yaml b/config/crd/bases/agentic.openshift.io_agents.yaml new file mode 100644 index 000000000..c713c4da7 --- /dev/null +++ b/config/crd/bases/agentic.openshift.io_agents.yaml @@ -0,0 +1,226 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + name: agents.agentic.openshift.io +spec: + group: agentic.openshift.io + names: + kind: Agent + listKind: AgentList + plural: agents + singular: agent + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .spec.llmProvider.name + name: LLM + type: string + - jsonPath: .spec.model + name: Model + type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: "Agent defines a cluster-scoped agent tier (e.g., \"default\", + \"smart\", \"fast\").\nThe cluster admin creates Agent resources to configure + LLM infrastructure\nand runtime settings. Proposals reference agents by + name per step.\n\nAgent is cluster-scoped. The metadata.name serves as the + tier identifier.\nThe \"default\" agent must exist; \"smart\" and \"fast\" + are optional (the\noperator auto-links to \"default\" if absent).\n\nExample + — a high-capability agent tier:\n\n\tapiVersion: agentic.openshift.io/v1alpha1\n\tkind: + Agent\n\tmetadata:\n\t name: smart\n\tspec:\n\t llmProvider:\n\t name: + vertex-ai\n\t model: claude-opus-4-6\n\t timeouts:\n\t analysisSeconds: + 300\n\t executionSeconds: 600\n\t maxTurns: 200\n\nExample — a fast, + cost-efficient agent tier:\n\n\tapiVersion: agentic.openshift.io/v1alpha1\n\tkind: + Agent\n\tmetadata:\n\t name: fast\n\tspec:\n\t llmProvider:\n\t name: + vertex-ai\n\t model: claude-haiku-4-5\n\t timeouts:\n\t analysisSeconds: + 120\n\t executionSeconds: 300\n\t maxTurns: 100" + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec defines the desired state of Agent. + properties: + llmProvider: + description: |- + llmProvider references a cluster-scoped LLMProvider CR that supplies the + LLM backend for this agent tier. + properties: + name: + description: name of the LLMProvider. Must be a valid RFC 1123 + DNS subdomain. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS subdomain: lowercase alphanumeric + characters, hyphens, and dots' + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + required: + - name + type: object + maxTurns: + description: |- + maxTurns is the maximum number of tool-use turns the agent may take + in a single step invocation. Prevents runaway loops. + When omitted, the agent sandbox uses its built-in default. + Minimum 1, maximum 500. + format: int32 + maximum: 500 + minimum: 1 + type: integer + model: + description: |- + model is the LLM model identifier as recognized by the provider + (e.g., "claude-opus-4-6", "claude-haiku-4-5", "gpt-4o"). + Must start with an alphanumeric character and may contain + alphanumerics, dots, hyphens, underscores, slashes, colons, + and at-signs. Maximum 256 characters. + maxLength: 256 + minLength: 1 + type: string + x-kubernetes-validations: + - message: model must start with an alphanumeric character and contain + only alphanumerics, dots, hyphens, underscores, slashes, colons, + and at-signs + rule: self.matches('^[a-zA-Z0-9][a-zA-Z0-9._\\-/:@]*$') + timeouts: + description: |- + timeouts configures per-step and per-turn timeout limits. + When omitted, the agent sandbox uses its built-in defaults. + minProperties: 1 + properties: + analysisSeconds: + description: analysisSeconds is the timeout for the analysis step + in seconds. + format: int32 + maximum: 3600 + minimum: 1 + type: integer + chatSeconds: + description: chatSeconds is the timeout for each chat turn with + the LLM in seconds. + format: int32 + maximum: 600 + minimum: 1 + type: integer + executionSeconds: + description: executionSeconds is the timeout for the execution + step in seconds. + format: int32 + maximum: 3600 + minimum: 1 + type: integer + verificationSeconds: + description: verificationSeconds is the timeout for the verification + step in seconds. + format: int32 + maximum: 3600 + minimum: 1 + type: integer + type: object + required: + - llmProvider + - model + type: object + status: + description: status defines the observed state of Agent. + minProperties: 1 + properties: + conditions: + description: |- + conditions represent the latest available observations of the + Agent's state. The Ready condition summarizes whether all + referenced resources (LLMProvider, Secrets) are present. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/agentic.openshift.io_analysisresults.yaml b/config/crd/bases/agentic.openshift.io_analysisresults.yaml new file mode 100644 index 000000000..62922c528 --- /dev/null +++ b/config/crd/bases/agentic.openshift.io_analysisresults.yaml @@ -0,0 +1,625 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + name: analysisresults.agentic.openshift.io +spec: + group: agentic.openshift.io + names: + kind: AnalysisResult + listKind: AnalysisResultList + plural: analysisresults + singular: analysisresult + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.proposalName + name: Proposal + type: string + - jsonPath: .status.conditions[?(@.type=="Completed")].reason + name: Outcome + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + AnalysisResult records the output of a single analysis step execution. + Created by the operator after the analysis agent completes. Owned by + the parent Proposal for garbage collection. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec contains the immutable identity fields for this result. + properties: + proposalName: + description: proposalName is the name of the parent Proposal in the + same namespace. + maxLength: 253 + minLength: 1 + type: string + required: + - proposalName + type: object + x-kubernetes-validations: + - message: spec is immutable + rule: self == oldSelf + status: + description: status contains result data and conditions. + minProperties: 1 + properties: + conditions: + description: conditions track the lifecycle of this result. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + failureReason: + description: failureReason is populated when the step failed due to + a system error. + maxLength: 8192 + minLength: 1 + type: string + options: + description: options contains the remediation options returned by + the analysis agent. + items: + description: |- + RemediationOption represents a single remediation approach produced by + the analysis agent. The agent may return multiple options, each with + its own diagnosis, remediation plan, verification strategy, and RBAC + requirements. When the user approves execution, the operator trims + the AnalysisResult to keep only the approved option and uses its + RBAC and plan for the execution step. + + The components field is an extensibility point for adapter-specific UI + data. For example, an ACS adapter might include violation details or + affected deployment information as components that the console plugin + renders with custom components. + properties: + components: + description: |- + components contains optional adapter-defined structured data whose + shape is determined by spec.analysisOutput.schema on the Proposal. + The operator passes this through to the AnalysisResult CR; the + console renders it using adapter-specific UI components. + x-kubernetes-preserve-unknown-fields: true + diagnosis: + description: |- + diagnosis contains the root cause analysis specific to this option. + Present when analysisOutput mode is Default (or omitted). Omitted + when mode is Minimal. + properties: + confidence: + description: |- + confidence is the agent's self-assessed confidence in its diagnosis. + Higher confidence generally correlates with clearer symptoms and + more deterministic root causes. + enum: + - Low + - Medium + - High + type: string + rootCause: + description: |- + rootCause is a concise Markdown-formatted description of the identified + root cause (e.g., "OOMKilled due to memory limit of 256Mi"). + Maximum 1024 characters. + maxLength: 1024 + minLength: 1 + type: string + summary: + description: |- + summary is a Markdown-formatted diagnosis summary explaining the + problem, its symptoms, and the agent's findings. Maximum 8192 characters. + maxLength: 8192 + minLength: 1 + type: string + required: + - confidence + - rootCause + - summary + type: object + proposal: + description: |- + proposal contains the remediation plan for this option. + Present when analysisOutput mode is Default (or omitted). Omitted + when mode is Minimal without an execution step. + properties: + actions: + description: |- + actions is the ordered list of discrete actions the agent proposes. + Maximum 50 items. + items: + description: |- + ProposedAction describes a single discrete action the analysis agent + recommends as part of its remediation plan. Actions are displayed to + the user after analysis for review before approval. + properties: + description: + description: |- + description is a Markdown-formatted explanation of what this action + will do (e.g., "Increase memory limit from 256Mi to 512Mi"). + Maximum 4096 characters. + maxLength: 4096 + minLength: 1 + type: string + type: + description: |- + type is the action category (e.g., "patch", "scale", "restart", + "create", "delete", "rollout"). Free-form string to allow agents + to express domain-specific action types. Must be 1-256 characters. + maxLength: 256 + minLength: 1 + type: string + required: + - description + - type + type: object + maxItems: 50 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + description: + description: |- + description is a Markdown-formatted summary of the overall remediation + approach. Maximum 8192 characters. + maxLength: 8192 + minLength: 1 + type: string + estimatedImpact: + description: |- + estimatedImpact is a Markdown-formatted description of the expected + impact of the remediation on the system + (e.g., "Brief pod restart, ~30s downtime"). + Maximum 1024 characters. + maxLength: 1024 + minLength: 1 + type: string + reversible: + description: |- + reversible indicates whether the remediation can be rolled back + if something goes wrong. See rollbackPlan for details. + Must be one of: Reversible, Irreversible, Partial. + enum: + - Reversible + - Irreversible + - Partial + type: string + risk: + description: |- + risk is the agent's assessment of how risky the remediation is. + Critical-risk proposals typically require explicit human review. + enum: + - Low + - Medium + - High + - Critical + type: string + rollbackPlan: + description: |- + rollbackPlan describes how to undo the remediation if execution fails + or causes unexpected issues. Only the execution step mutates cluster + state, so rollback lives here alongside the actions it would undo. + properties: + command: + description: |- + command is the rollback command or steps to execute. + Maximum 4096 characters. + maxLength: 4096 + minLength: 1 + type: string + description: + description: |- + description is a Markdown-formatted explanation of the rollback strategy. + Must be 1-4096 characters. + maxLength: 4096 + minLength: 1 + type: string + required: + - description + type: object + required: + - actions + - description + - estimatedImpact + - risk + type: object + rbac: + description: |- + rbac contains the RBAC permissions the execution agent will need. + The operator's policy engine validates these before creating the + actual Kubernetes RBAC resources. Omitted for advisory-only options. + minProperties: 1 + properties: + clusterScoped: + description: |- + clusterScoped are rules that will be applied via ClusterRole + + ClusterRoleBinding. Used when the agent needs cross-namespace or + non-namespaced resource access (e.g., reading nodes, CRDs). + Maximum 50 items. + items: + description: |- + RBACRule describes a single RBAC permission that the analysis agent + requests for the execution step. The operator's policy engine validates + these requests against a 6-layer defense model before creating the + actual Role/ClusterRole bindings. Each rule must include a justification + so that users and policy can audit why the permission is needed. + properties: + apiGroups: + description: |- + apiGroups are the API groups for this rule (e.g., "", "apps", "batch"). + The empty string "" represents the core API group (pods, services, etc.). + Maximum 20 items, each up to 253 characters. + items: + maxLength: 253 + type: string + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + justification: + description: |- + justification is a Markdown-formatted explanation of why this + permission is needed for the remediation + (e.g., "Need to patch deployment to increase memory limit"). + Required for audit and policy enforcement. Maximum 1024 characters. + maxLength: 1024 + minLength: 1 + type: string + namespace: + description: |- + namespace is the target namespace for namespace-scoped rules. + Must match one of the proposal's targetNamespaces. Ignored for + cluster-scoped rules. Validation is deferred to the operator's + policy engine at runtime. Must be a valid RFC 1123 DNS label. + maxLength: 63 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS label: lowercase alphanumeric + characters and hyphens, starting with an alphabetic + character and ending with an alphanumeric character' + rule: '!format.dns1123Label().validate(self).hasValue()' + resourceNames: + description: |- + resourceNames restricts the rule to specific named resources. + When empty, the rule applies to all resources of the given type. + Maximum 50 items. + items: + maxLength: 253 + minLength: 1 + type: string + maxItems: 50 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + resources: + description: |- + resources are the resource types (e.g., "pods", "deployments"). + Maximum 20 items. + items: + maxLength: 253 + minLength: 1 + type: string + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + verbs: + description: |- + verbs are the allowed operations (e.g., "get", "patch", "delete"). + Maximum 10 items. + items: + maxLength: 63 + minLength: 1 + type: string + maxItems: 10 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + required: + - apiGroups + - justification + - resources + - verbs + type: object + maxItems: 50 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + namespaceScoped: + description: |- + namespaceScoped are rules that will be applied via Role + RoleBinding + in the proposal's target namespaces. These are the most common rules. + Maximum 50 items. + items: + description: |- + RBACRule describes a single RBAC permission that the analysis agent + requests for the execution step. The operator's policy engine validates + these requests against a 6-layer defense model before creating the + actual Role/ClusterRole bindings. Each rule must include a justification + so that users and policy can audit why the permission is needed. + properties: + apiGroups: + description: |- + apiGroups are the API groups for this rule (e.g., "", "apps", "batch"). + The empty string "" represents the core API group (pods, services, etc.). + Maximum 20 items, each up to 253 characters. + items: + maxLength: 253 + type: string + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + justification: + description: |- + justification is a Markdown-formatted explanation of why this + permission is needed for the remediation + (e.g., "Need to patch deployment to increase memory limit"). + Required for audit and policy enforcement. Maximum 1024 characters. + maxLength: 1024 + minLength: 1 + type: string + namespace: + description: |- + namespace is the target namespace for namespace-scoped rules. + Must match one of the proposal's targetNamespaces. Ignored for + cluster-scoped rules. Validation is deferred to the operator's + policy engine at runtime. Must be a valid RFC 1123 DNS label. + maxLength: 63 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS label: lowercase alphanumeric + characters and hyphens, starting with an alphabetic + character and ending with an alphanumeric character' + rule: '!format.dns1123Label().validate(self).hasValue()' + resourceNames: + description: |- + resourceNames restricts the rule to specific named resources. + When empty, the rule applies to all resources of the given type. + Maximum 50 items. + items: + maxLength: 253 + minLength: 1 + type: string + maxItems: 50 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + resources: + description: |- + resources are the resource types (e.g., "pods", "deployments"). + Maximum 20 items. + items: + maxLength: 253 + minLength: 1 + type: string + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + verbs: + description: |- + verbs are the allowed operations (e.g., "get", "patch", "delete"). + Maximum 10 items. + items: + maxLength: 63 + minLength: 1 + type: string + maxItems: 10 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + required: + - apiGroups + - justification + - resources + - verbs + type: object + maxItems: 50 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + type: object + summary: + description: |- + summary is an optional Markdown-formatted one-line summary for + collapsed views in the console UI. Maximum 1024 characters. + maxLength: 1024 + minLength: 1 + type: string + title: + description: |- + title is a short Markdown-formatted name for this option + (e.g., "Increase memory limit", "Restart with backoff"). + Must be 1-256 characters. + maxLength: 256 + minLength: 1 + type: string + verification: + description: |- + verification contains the verification plan. Omitted when + verification is skipped in the workflow. + properties: + description: + description: |- + description is a Markdown-formatted summary of the verification approach. + Maximum 4096 characters. + maxLength: 4096 + minLength: 1 + type: string + steps: + description: |- + steps is the ordered list of verification checks to run. + Maximum 20 items. + items: + description: |- + VerificationStep describes a single verification check that the + verification agent should run after execution. Populated by the + analysis agent as part of the RemediationOption. + properties: + command: + description: |- + command is the command or API call to run for this check + (e.g., "oc get pod -n production -l app=web -o jsonpath='{.items[0].status.phase}'"). + Maximum 4096 characters. + maxLength: 4096 + minLength: 1 + type: string + expected: + description: |- + expected is the expected output or condition + (e.g., "Running", "ready=true"). Maximum 1024 characters. + maxLength: 1024 + minLength: 1 + type: string + name: + description: |- + name is a short identifier for this check (e.g., "pod-running"). + Must be 1-253 characters. + maxLength: 253 + minLength: 1 + type: string + type: + description: |- + type categorizes the check (e.g., "command", "metric", "condition"). + Must be 1-256 characters. + maxLength: 256 + minLength: 1 + type: string + required: + - name + - type + type: object + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + required: + - description + type: object + required: + - title + type: object + x-kubernetes-validations: + - message: proposal is required when diagnosis is present + rule: '!has(self.diagnosis) || has(self.proposal)' + - message: diagnosis is required when proposal is present + rule: '!has(self.proposal) || has(self.diagnosis)' + maxItems: 10 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + sandbox: + description: sandbox tracks the sandbox pod used for this analysis. + properties: + claimName: + description: |- + claimName is the name of the SandboxClaim resource that owns the + sandbox pod. Maximum 253 characters. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + namespace is the namespace where the SandboxClaim and its pod live. + Must be a valid RFC 1123 DNS label. + maxLength: 63 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS label: lowercase alphanumeric + characters and hyphens, starting with an alphabetic character + and ending with an alphanumeric character' + rule: '!format.dns1123Label().validate(self).hasValue()' + required: + - claimName + - namespace + type: object + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/agentic.openshift.io_approvalpolicies.yaml b/config/crd/bases/agentic.openshift.io_approvalpolicies.yaml new file mode 100644 index 000000000..007345a7e --- /dev/null +++ b/config/crd/bases/agentic.openshift.io_approvalpolicies.yaml @@ -0,0 +1,122 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + name: approvalpolicies.agentic.openshift.io +spec: + group: agentic.openshift.io + names: + kind: ApprovalPolicy + listKind: ApprovalPolicyList + plural: approvalpolicies + singular: approvalpolicy + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: "ApprovalPolicy is a cluster-scoped singleton that configures + default\napproval behavior for proposal workflow steps. The cluster admin + creates\na single ApprovalPolicy named \"cluster\" to control which steps + auto-approve.\n\nSteps not listed in the policy default to Manual (require + explicit\nuser approval on the ProposalApproval resource).\n\nExample:\n\n\tapiVersion: + agentic.openshift.io/v1alpha1\n\tkind: ApprovalPolicy\n\tmetadata:\n\t name: + cluster\n\tspec:\n\t stages:\n\t - name: Analysis\n\t approval: + Automatic\n\t - name: Execution\n\t approval: Manual\n\t - name: + Verification\n\t approval: Automatic" + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec defines the desired approval policy. + minProperties: 1 + properties: + maxAttempts: + description: |- + maxAttempts sets the maximum number of execution retry attempts + allowed for proposals. When verification fails, the operator retries + execution up to this limit before escalating. Defaults to 1 if omitted. + format: int32 + maximum: 3 + minimum: 1 + type: integer + maxConcurrentProposals: + default: 5 + description: |- + maxConcurrentProposals sets the maximum number of proposals the + operator reconciles concurrently. Higher values allow more proposals + to run in parallel but consume more cluster resources. + Defaults to 5 if omitted. + format: int32 + maximum: 20 + minimum: 1 + type: integer + stages: + description: |- + stages configures the approval mode for each workflow step. + Omitted steps default to Manual. + items: + description: ApprovalPolicyStage configures the approval mode for + a single workflow step. + properties: + approval: + description: |- + approval controls whether this step auto-approves or requires + explicit user approval on the ProposalApproval resource. + Allowed values: Automatic (step runs without user approval), + Manual (step waits for explicit approval on ProposalApproval). + enum: + - Automatic + - Manual + type: string + name: + description: |- + name is the workflow step this policy applies to. + Allowed values: Analysis, Execution, Verification, Escalation. + enum: + - Analysis + - Execution + - Verification + - Escalation + type: string + required: + - approval + - name + type: object + maxItems: 4 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + x-kubernetes-validations: + - message: ApprovalPolicy must be named 'cluster' (singleton) + rule: self.metadata.name == 'cluster' + served: true + storage: true + subresources: {} diff --git a/config/crd/bases/agentic.openshift.io_escalationresults.yaml b/config/crd/bases/agentic.openshift.io_escalationresults.yaml new file mode 100644 index 000000000..bc54ccabe --- /dev/null +++ b/config/crd/bases/agentic.openshift.io_escalationresults.yaml @@ -0,0 +1,183 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + name: escalationresults.agentic.openshift.io +spec: + group: agentic.openshift.io + names: + kind: EscalationResult + listKind: EscalationResultList + plural: escalationresults + singular: escalationresult + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.proposalName + name: Proposal + type: string + - jsonPath: .status.conditions[?(@.type=="Completed")].reason + name: Outcome + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + EscalationResult records the output of the escalation step. Created by + the operator after the escalation agent completes. Owned by the parent + Proposal for garbage collection. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec contains the immutable identity fields for this result. + properties: + proposalName: + description: proposalName is the name of the parent Proposal in the + same namespace. + maxLength: 253 + minLength: 1 + type: string + required: + - proposalName + type: object + x-kubernetes-validations: + - message: spec is immutable + rule: self == oldSelf + status: + description: status contains result data and conditions. + minProperties: 1 + properties: + conditions: + description: conditions track the lifecycle of this result. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + content: + description: content is freeform escalation content produced by the + agent. + maxLength: 65536 + minLength: 1 + type: string + failureReason: + description: failureReason is populated when the step failed due to + a system error. + maxLength: 8192 + minLength: 1 + type: string + sandbox: + description: sandbox tracks the sandbox pod used for this escalation. + properties: + claimName: + description: |- + claimName is the name of the SandboxClaim resource that owns the + sandbox pod. Maximum 253 characters. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + namespace is the namespace where the SandboxClaim and its pod live. + Must be a valid RFC 1123 DNS label. + maxLength: 63 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS label: lowercase alphanumeric + characters and hyphens, starting with an alphabetic character + and ending with an alphanumeric character' + rule: '!format.dns1123Label().validate(self).hasValue()' + required: + - claimName + - namespace + type: object + summary: + description: summary is a Markdown-formatted escalation summary. + maxLength: 32768 + minLength: 1 + type: string + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/agentic.openshift.io_executionresults.yaml b/config/crd/bases/agentic.openshift.io_executionresults.yaml new file mode 100644 index 000000000..e31a48831 --- /dev/null +++ b/config/crd/bases/agentic.openshift.io_executionresults.yaml @@ -0,0 +1,264 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + name: executionresults.agentic.openshift.io +spec: + group: agentic.openshift.io + names: + kind: ExecutionResult + listKind: ExecutionResultList + plural: executionresults + singular: executionresult + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.proposalName + name: Proposal + type: string + - jsonPath: .spec.retryIndex + name: Retry + type: integer + - jsonPath: .status.conditions[?(@.type=="Completed")].reason + name: Outcome + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + ExecutionResult records the output of a single execution step execution. + Created by the operator after the execution agent completes. Owned by + the parent Proposal for garbage collection. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec contains the immutable identity fields for this result. + properties: + proposalName: + description: proposalName is the name of the parent Proposal in the + same namespace. + maxLength: 253 + minLength: 1 + type: string + retryIndex: + description: |- + retryIndex is the 0-based retry index within the current analysis. + First execution has retryIndex 0, first retry has retryIndex 1, etc. + format: int32 + maximum: 2 + minimum: 0 + type: integer + required: + - proposalName + - retryIndex + type: object + x-kubernetes-validations: + - message: spec is immutable + rule: self == oldSelf + status: + description: status contains result data and conditions. + minProperties: 1 + properties: + actionsTaken: + description: actionsTaken lists what the agent did. + items: + description: |- + ExecutionAction describes a single action taken by the execution agent + during the execution step. These are recorded in ExecutionStepStatus + to provide an audit trail of what the agent actually did. + properties: + description: + description: |- + description is a Markdown-formatted explanation of what the agent did + (e.g., "Patched deployment/web to set memory limit to 512Mi"). + Maximum 4096 characters. + maxLength: 4096 + minLength: 1 + type: string + error: + description: |- + error is the error message if the action failed. + Maximum 8192 characters. + maxLength: 8192 + minLength: 1 + type: string + outcome: + description: |- + outcome indicates whether this individual action succeeded. + Must be one of: Succeeded, Failed. + enum: + - Succeeded + - Failed + type: string + output: + description: |- + output is the command output or API response from the action. + Maximum 32768 characters. + maxLength: 32768 + minLength: 1 + type: string + type: + description: |- + type is the action category (e.g., "patch", "scale", "restart"). + Maximum 256 characters. + maxLength: 256 + minLength: 1 + type: string + required: + - description + - outcome + - type + type: object + maxItems: 100 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + conditions: + description: conditions track the lifecycle of this result. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + failureReason: + description: failureReason is populated when the step failed due to + a system error. + maxLength: 8192 + minLength: 1 + type: string + sandbox: + description: sandbox tracks the sandbox pod used for this execution. + properties: + claimName: + description: |- + claimName is the name of the SandboxClaim resource that owns the + sandbox pod. Maximum 253 characters. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + namespace is the namespace where the SandboxClaim and its pod live. + Must be a valid RFC 1123 DNS label. + maxLength: 63 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS label: lowercase alphanumeric + characters and hyphens, starting with an alphabetic character + and ending with an alphanumeric character' + rule: '!format.dns1123Label().validate(self).hasValue()' + required: + - claimName + - namespace + type: object + verification: + description: |- + verification is the lightweight inline verification the execution + agent performs immediately after completing its actions. + properties: + conditionOutcome: + description: |- + conditionOutcome indicates whether the target condition improved + after the remediation (e.g., pod is no longer CrashLoopBackOff). + Must be one of: Improved, Unchanged, Degraded. + enum: + - Improved + - Unchanged + - Degraded + type: string + summary: + description: |- + summary is a Markdown-formatted summary of the inline verification. + Maximum 4096 characters. + maxLength: 4096 + minLength: 1 + type: string + required: + - conditionOutcome + - summary + type: object + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/agentic.openshift.io_llmproviders.yaml b/config/crd/bases/agentic.openshift.io_llmproviders.yaml new file mode 100644 index 000000000..d91671385 --- /dev/null +++ b/config/crd/bases/agentic.openshift.io_llmproviders.yaml @@ -0,0 +1,436 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + name: llmproviders.agentic.openshift.io +spec: + group: agentic.openshift.io + names: + kind: LLMProvider + listKind: LLMProviderList + plural: llmproviders + singular: llmprovider + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .spec.type + name: Type + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: "LLMProvider defines an LLM provider configuration. It is the + first link in\nthe CRD chain (LLMProvider -> Agent -> Workflow -> Proposal) + and is\nreferenced by Agent resources via spec.llmProvider.\n\nLLMProvider + is cluster-scoped — the cluster admin manages LLM infrastructure\ncentrally. + The operator uses the credentials to configure the LLM client\ninside agent + sandbox pods. The model is specified on the Agent CR, allowing\nmultiple + agents to share one LLMProvider with different models.\n\nTypically you + create one provider per backend (e.g., one for Vertex AI)\nand then reference + it from multiple Agent resources with different models.\n\nExample — a Vertex + AI provider (model specified on Agent, not here):\n\n\tapiVersion: agentic.openshift.io/v1alpha1\n\tkind: + LLMProvider\n\tmetadata:\n\t name: vertex-ai\n\tspec:\n\t type: GoogleCloudVertex\n\t + \ googleCloudVertex:\n\t credentialsSecret:\n\t name: llm-credentials\n\t + \ projectID: my-gcp-project\n\t region: us-central1\n\t modelProvider: + Anthropic" + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec defines the desired state of LLMProvider. + properties: + anthropic: + description: |- + anthropic contains Anthropic-specific configuration. + Required when type is "Anthropic". + properties: + credentialsSecret: + description: |- + credentialsSecret references a Secret in the operator namespace + (openshift-lightspeed) containing the Anthropic API credentials. + The Secret must contain the key ANTHROPIC_API_KEY. + properties: + name: + description: name of the Secret. Must be a valid RFC 1123 + DNS subdomain. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS subdomain: lowercase alphanumeric + characters, hyphens, and dots' + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + required: + - name + type: object + url: + description: |- + url is an optional override for the Anthropic API endpoint. + Only needed for custom deployments or API proxies. + Must be a valid HTTP or HTTPS URL with a hostname. Paths and query + parameters are allowed. Fragments and userinfo are not permitted. + Maximum 2048 characters. + maxLength: 2048 + minLength: 1 + type: string + x-kubernetes-validations: + - message: must use http or https scheme + rule: isURL(self) && url(self).getScheme() in ['http', 'https'] + - message: must include a hostname + rule: isURL(self) && url(self).getHostname() != '' + - message: userinfo is not allowed in URL + rule: '!self.contains(''@'')' + - message: fragments are not allowed in URL + rule: '!self.contains(''#'')' + required: + - credentialsSecret + type: object + awsBedrock: + description: |- + awsBedrock contains AWS Bedrock-specific configuration. + Required when type is "AWSBedrock". + properties: + credentialsSecret: + description: |- + credentialsSecret references a Secret in the operator namespace + (openshift-lightspeed) containing AWS credentials. The Secret must + contain the keys AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY. + properties: + name: + description: name of the Secret. Must be a valid RFC 1123 + DNS subdomain. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS subdomain: lowercase alphanumeric + characters, hyphens, and dots' + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + required: + - name + type: object + region: + description: |- + region is the AWS region for the Bedrock endpoint. + Must begin with a lowercase letter and end with a lowercase + alphanumeric character. May contain lowercase letters, digits, + and hyphens (e.g., "us-east-1", "eu-west-2", "ap-southeast-1"). + maxLength: 63 + minLength: 2 + type: string + x-kubernetes-validations: + - message: region must contain only lowercase letters, digits, + and hyphens, start with a letter, and not end with a hyphen + rule: self.matches('^[a-z][a-z0-9-]*[a-z0-9]$') + url: + description: |- + url is an optional override for the AWS Bedrock API endpoint. + Only needed for custom deployments or API proxies. + Must be a valid HTTP or HTTPS URL with a hostname. Paths and query + parameters are allowed. Fragments and userinfo are not permitted. + Maximum 2048 characters. + maxLength: 2048 + minLength: 1 + type: string + x-kubernetes-validations: + - message: must use http or https scheme + rule: isURL(self) && url(self).getScheme() in ['http', 'https'] + - message: must include a hostname + rule: isURL(self) && url(self).getHostname() != '' + - message: userinfo is not allowed in URL + rule: '!self.contains(''@'')' + - message: fragments are not allowed in URL + rule: '!self.contains(''#'')' + required: + - credentialsSecret + - region + type: object + azureOpenAI: + description: |- + azureOpenAI contains Azure OpenAI Service-specific configuration. + Required when type is "AzureOpenAI". + properties: + apiVersion: + description: |- + apiVersion is the Azure OpenAI API version. Azure API versions use + a date-based format: YYYY-MM-DD with an optional "-preview" suffix + (e.g., "2024-02-01", "2024-08-01-preview"). + When omitted, the SDK default is used. + maxLength: 32 + minLength: 1 + type: string + x-kubernetes-validations: + - message: apiVersion must be a date in YYYY-MM-DD format with + an optional -preview suffix + rule: self.matches('^[0-9]{4}-[0-9]{2}-[0-9]{2}(-preview)?$') + credentialsSecret: + description: |- + credentialsSecret references a Secret in the operator namespace + (openshift-lightspeed) containing the Azure OpenAI API credentials. + The Secret must contain the key AZURE_OPENAI_API_KEY. + properties: + name: + description: name of the Secret. Must be a valid RFC 1123 + DNS subdomain. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS subdomain: lowercase alphanumeric + characters, hyphens, and dots' + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + required: + - name + type: object + endpoint: + description: |- + endpoint is the Azure OpenAI resource endpoint + (e.g., "https://my-resource.openai.azure.com"). + Must be a valid HTTP or HTTPS URL with a hostname. Paths and query + parameters are allowed. Fragments and userinfo are not permitted. + Maximum 2048 characters. + maxLength: 2048 + minLength: 1 + type: string + x-kubernetes-validations: + - message: must use http or https scheme + rule: isURL(self) && url(self).getScheme() in ['http', 'https'] + - message: must include a hostname + rule: isURL(self) && url(self).getHostname() != '' + - message: userinfo is not allowed in URL + rule: '!self.contains(''@'')' + - message: fragments are not allowed in URL + rule: '!self.contains(''#'')' + url: + description: |- + url is an optional override for the Azure OpenAI API endpoint. + Only needed for custom deployments or API proxies. This is separate + from the required 'endpoint' field which identifies the Azure resource. + Must be a valid HTTP or HTTPS URL with a hostname. Paths and query + parameters are allowed. Fragments and userinfo are not permitted. + Maximum 2048 characters. + maxLength: 2048 + minLength: 1 + type: string + x-kubernetes-validations: + - message: must use http or https scheme + rule: isURL(self) && url(self).getScheme() in ['http', 'https'] + - message: must include a hostname + rule: isURL(self) && url(self).getHostname() != '' + - message: userinfo is not allowed in URL + rule: '!self.contains(''@'')' + - message: fragments are not allowed in URL + rule: '!self.contains(''#'')' + required: + - credentialsSecret + - endpoint + type: object + googleCloudVertex: + description: |- + googleCloudVertex contains Google Cloud Vertex AI-specific configuration. + Required when type is "GoogleCloudVertex". + properties: + credentialsSecret: + description: |- + credentialsSecret references a Secret in the operator namespace + (openshift-lightspeed) containing a GCP service account JSON key. + The Secret must contain the key GOOGLE_APPLICATION_CREDENTIALS. + properties: + name: + description: name of the Secret. Must be a valid RFC 1123 + DNS subdomain. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS subdomain: lowercase alphanumeric + characters, hyphens, and dots' + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + required: + - name + type: object + modelProvider: + description: |- + modelProvider selects which model provider stack talks to Vertex AI (required). + "Anthropic" uses Anthropic models on Vertex; "Google" uses Google models on Vertex; + "OpenAI" uses OpenAI-compatible models on Vertex. + Enum is defined on GoogleCloudVertexModelProvider. + enum: + - Anthropic + - Google + - OpenAI + type: string + projectID: + description: |- + projectID is the Google Cloud Project ID where Vertex AI is enabled. + A Project ID is a globally unique identifier that must be 6 to 30 + characters in length, can only contain lowercase letters, digits, and + hyphens, must start with a letter, and cannot end with a hyphen. + maxLength: 30 + minLength: 6 + type: string + x-kubernetes-validations: + - message: projectID must start with a lowercase letter, contain + only lowercase letters, digits, and hyphens, and cannot end + with a hyphen + rule: self.matches('^[a-z][a-z0-9-]*[a-z0-9]$') + region: + description: |- + region is the GCP region for the Vertex AI endpoint. + Must begin with a lowercase letter and end with a lowercase + alphanumeric character. May contain lowercase letters, digits, + and hyphens (e.g., "us-central1", "europe-west4", "asia-southeast1"). + maxLength: 63 + minLength: 2 + type: string + x-kubernetes-validations: + - message: region must contain only lowercase letters, digits, + and hyphens, start with a letter, and not end with a hyphen + rule: self.matches('^[a-z][a-z0-9-]*[a-z0-9]$') + url: + description: |- + url is an optional override for the Vertex AI API endpoint. + Only needed for custom deployments or API proxies. + Must be a valid HTTP or HTTPS URL with a hostname. Paths and query + parameters are allowed. Fragments and userinfo are not permitted. + Maximum 2048 characters. + maxLength: 2048 + minLength: 1 + type: string + x-kubernetes-validations: + - message: must use http or https scheme + rule: isURL(self) && url(self).getScheme() in ['http', 'https'] + - message: must include a hostname + rule: isURL(self) && url(self).getHostname() != '' + - message: userinfo is not allowed in URL + rule: '!self.contains(''@'')' + - message: fragments are not allowed in URL + rule: '!self.contains(''#'')' + required: + - credentialsSecret + - modelProvider + - projectID + - region + type: object + openAI: + description: |- + openAI contains OpenAI-specific configuration. + Required when type is "OpenAI". + properties: + credentialsSecret: + description: |- + credentialsSecret references a Secret in the operator namespace + (openshift-lightspeed) containing the OpenAI API credentials. + The Secret must contain the key OPENAI_API_KEY. + properties: + name: + description: name of the Secret. Must be a valid RFC 1123 + DNS subdomain. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS subdomain: lowercase alphanumeric + characters, hyphens, and dots' + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + required: + - name + type: object + url: + description: |- + url is an optional override for the OpenAI API endpoint. + Only needed for custom deployments or API proxies. + Must be a valid HTTP or HTTPS URL with a hostname. Paths and query + parameters are allowed. Fragments and userinfo are not permitted. + Maximum 2048 characters. + maxLength: 2048 + minLength: 1 + type: string + x-kubernetes-validations: + - message: must use http or https scheme + rule: isURL(self) && url(self).getScheme() in ['http', 'https'] + - message: must include a hostname + rule: isURL(self) && url(self).getHostname() != '' + - message: userinfo is not allowed in URL + rule: '!self.contains(''@'')' + - message: fragments are not allowed in URL + rule: '!self.contains(''#'')' + required: + - credentialsSecret + type: object + type: + description: |- + type is a required field that configures which LLM provider backend + should be used. + + Allowed values are Anthropic, GoogleCloudVertex, OpenAI, AzureOpenAI, + and AWSBedrock. + + When set to Anthropic, agents referencing this provider will use the + Anthropic API directly, and the 'anthropic' field must be configured. + + When set to GoogleCloudVertex, agents referencing this provider will + use Google Cloud Vertex AI, and the 'googleCloudVertex' field must be + configured. + + When set to OpenAI, agents referencing this provider will use an + OpenAI-compatible API, and the 'openAI' field must be configured. + + When set to AzureOpenAI, agents referencing this provider will use + the Azure OpenAI Service, and the 'azureOpenAI' field must be + configured. + + When set to AWSBedrock, agents referencing this provider will use + AWS Bedrock, and the 'awsBedrock' field must be configured. + enum: + - Anthropic + - GoogleCloudVertex + - OpenAI + - AzureOpenAI + - AWSBedrock + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: anthropic is required when type is Anthropic, and forbidden + otherwise + rule: 'self.type == ''Anthropic'' ? has(self.anthropic) : !has(self.anthropic)' + - message: googleCloudVertex is required when type is GoogleCloudVertex, + and forbidden otherwise + rule: 'self.type == ''GoogleCloudVertex'' ? has(self.googleCloudVertex) + : !has(self.googleCloudVertex)' + - message: openAI is required when type is OpenAI, and forbidden otherwise + rule: 'self.type == ''OpenAI'' ? has(self.openAI) : !has(self.openAI)' + - message: azureOpenAI is required when type is AzureOpenAI, and forbidden + otherwise + rule: 'self.type == ''AzureOpenAI'' ? has(self.azureOpenAI) : !has(self.azureOpenAI)' + - message: awsBedrock is required when type is AWSBedrock, and forbidden + otherwise + rule: 'self.type == ''AWSBedrock'' ? has(self.awsBedrock) : !has(self.awsBedrock)' + required: + - spec + type: object + served: true + storage: true + subresources: {} diff --git a/config/crd/bases/agentic.openshift.io_proposalapprovals.yaml b/config/crd/bases/agentic.openshift.io_proposalapprovals.yaml new file mode 100644 index 000000000..41724f288 --- /dev/null +++ b/config/crd/bases/agentic.openshift.io_proposalapprovals.yaml @@ -0,0 +1,302 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + name: proposalapprovals.agentic.openshift.io +spec: + group: agentic.openshift.io + names: + kind: ProposalApproval + listKind: ProposalApprovalList + plural: proposalapprovals + singular: proposalapproval + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: "ProposalApproval tracks per-step approval state for a Proposal. + The\noperator creates it when a Proposal is created. Users update it to\napprove + or deny individual workflow steps.\n\nProposalApproval has a 1:1 relationship + with its Proposal (same name,\nsame namespace) and is owned by the Proposal + via an owner reference\nfor garbage collection.\n\nExample:\n\n\tapiVersion: + agentic.openshift.io/v1alpha1\n\tkind: ProposalApproval\n\tmetadata:\n\t + \ name: fix-crash\n\t namespace: my-namespace\n\t ownerReferences:\n\t + \ - apiVersion: agentic.openshift.io/v1alpha1\n\t kind: Proposal\n\t + \ name: fix-crash\n\tspec:\n\t stages:\n\t - type: Analysis\n\t + \ analysis: {}\n\t - type: Execution\n\t execution:\n\t option: + 0\n\t agent: fast" + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec defines the desired approval state. + minProperties: 1 + properties: + stages: + description: |- + stages lists the approved (or denied) workflow steps. Each entry is + a discriminated union keyed by type. Users add stages one at a time + via patch as they approve each step. + items: + description: |- + ApprovalStage is a discriminated union representing approval for one + workflow step. Presence in spec.stages indicates approval; absence means + not yet approved (controller checks ApprovalPolicy for auto-approve). + properties: + analysis: + description: |- + analysis contains approval parameters for the analysis step. + Required when type is Analysis. + minProperties: 1 + properties: + agent: + default: default + description: agent is the Agent CR for this step. Defaults + to "default". + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS subdomain: lowercase alphanumeric + characters, hyphens, and dots' + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + type: object + decision: + description: |- + decision indicates whether this stage is approved or denied. + Denying any stage terminates the entire proposal, even if + earlier stages were already approved. Once set to Denied, + it cannot be changed. + enum: + - Approved + - Denied + type: string + escalation: + description: |- + escalation contains approval parameters for the escalation step. + Required when type is Escalation. + minProperties: 1 + properties: + agent: + default: default + description: agent is the Agent CR for this step. Defaults + to "default". + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS subdomain: lowercase alphanumeric + characters, hyphens, and dots' + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + type: object + execution: + description: |- + execution contains approval parameters for the execution step. + Required when type is Execution. + minProperties: 1 + properties: + agent: + default: default + description: agent is the Agent CR for this step. Defaults + to "default". + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS subdomain: lowercase alphanumeric + characters, hyphens, and dots' + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + maxAttempts: + description: |- + maxAttempts is the number of execution retry attempts approved + for this proposal. Must not exceed ApprovalPolicy.spec.maxAttempts. + Defaults to 1 if unset. + format: int32 + maximum: 3 + minimum: 1 + type: integer + option: + description: |- + option is the 0-based index into the analysis options array + selecting which remediation approach to execute. + format: int32 + minimum: 0 + type: integer + type: object + type: + description: type identifies which workflow step this approval + is for. + enum: + - Analysis + - Execution + - Verification + - Escalation + type: string + verification: + description: |- + verification contains approval parameters for the verification step. + Required when type is Verification. + minProperties: 1 + properties: + agent: + default: default + description: agent is the Agent CR for this step. Defaults + to "default". + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS subdomain: lowercase alphanumeric + characters, hyphens, and dots' + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + type: object + required: + - type + type: object + x-kubernetes-validations: + - message: analysis is required when type is Analysis, and forbidden + otherwise + rule: 'self.type == ''Analysis'' ? has(self.analysis) : !has(self.analysis)' + - message: execution is required when type is Execution, and forbidden + otherwise + rule: 'self.type == ''Execution'' ? has(self.execution) : !has(self.execution)' + - message: verification is required when type is Verification, and + forbidden otherwise + rule: 'self.type == ''Verification'' ? has(self.verification) + : !has(self.verification)' + - message: escalation is required when type is Escalation, and forbidden + otherwise + rule: 'self.type == ''Escalation'' ? has(self.escalation) : !has(self.escalation)' + maxItems: 4 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + x-kubernetes-validations: + - message: 'stages are append-only: existing stages cannot be removed' + rule: oldSelf.stages.all(old, self.stages.exists(s, s.type == old.type)) + - message: decisions once set cannot be changed + rule: oldSelf.stages.all(old, !(has(old.decision) && old.decision == + 'Denied') || self.stages.exists(s, s.type == old.type && has(s.decision) + && s.decision == 'Denied')) + - message: maxAttempts once set cannot be changed + rule: oldSelf.stages.all(old, old.type != 'Execution' || !has(old.execution) + || !has(old.execution.maxAttempts) || old.execution.maxAttempts == + 0 || self.stages.exists(s, s.type == 'Execution' && has(s.execution) + && has(s.execution.maxAttempts) && s.execution.maxAttempts == old.execution.maxAttempts)) + status: + description: status defines the observed approval state. + minProperties: 1 + properties: + stages: + description: stages contains the per-stage approval status set by + the controller. + items: + description: ApprovalStageStatus is the observed state of a single + approval stage. + properties: + conditions: + description: conditions for this approval stage. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + name: + description: name identifies the workflow step. + maxLength: 253 + minLength: 1 + type: string + required: + - name + type: object + maxItems: 4 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/agentic.openshift.io_proposals.yaml b/config/crd/bases/agentic.openshift.io_proposals.yaml new file mode 100644 index 000000000..c6dadf1eb --- /dev/null +++ b/config/crd/bases/agentic.openshift.io_proposals.yaml @@ -0,0 +1,2278 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + name: proposals.agentic.openshift.io +spec: + group: agentic.openshift.io + names: + kind: Proposal + listKind: ProposalList + plural: proposals + singular: proposal + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.request + name: Request + priority: 1 + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: "Proposal represents a unit of work managed by the agentic platform.\nIt + is the primary resource component teams and adapters interact with.\n\nA + Proposal defines the workflow shape inline: which steps run and which\nagent + handles each step. Analysis is always required. Omit execution\nand/or verification + to skip those steps.\n\nExample — analysis only (advisory):\n\n\tapiVersion: + agentic.openshift.io/v1alpha1\n\tkind: Proposal\n\tmetadata:\n\t name: + one-off-investigation\n\tspec:\n\t request: \"Investigate why pod foo is + crashlooping\"\n\t targetNamespaces:\n\t - lightspeed-demo\n\t tools:\n\t + \ skills:\n\t - image: registry.redhat.io/acs/acs-agentic-skills:latest\n\t + \ analysis:\n\t agent: smart\n\nExample — full remediation (analyze → + execute → verify):\n\n\tapiVersion: agentic.openshift.io/v1alpha1\n\tkind: + Proposal\n\tmetadata:\n\t name: fix-nginx-cve-2024-1234\n\t namespace: + stackrox\n\tspec:\n\t request: \"Fix CVE-2024-1234 in nginx:1.21\"\n\t + \ targetNamespaces:\n\t - lightspeed-demo\n\t tools:\n\t skills:\n\t + \ - image: registry.redhat.io/acs/acs-agentic-skills:latest\n\t requiredSecrets:\n\t + \ - name: acs-api-token\n\t mountAs:\n\t type: EnvVar\n\t + \ envVar:\n\t name: ACS_API_TOKEN\n\t analysis:\n\t + \ agent: smart\n\t execution: {}\n\t verification:\n\t agent: fast" + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec defines the desired state of Proposal. + properties: + analysis: + description: |- + analysis defines per-step configuration for the analysis step, + including which agent handles it and any per-step tools. + + Immutable: agent and per-step tools are fixed at creation. + minProperties: 1 + properties: + agent: + description: |- + agent is the name of the cluster-scoped Agent CR to use for this step. + Defaults to "default" when omitted. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS subdomain: lowercase alphanumeric + characters, hyphens, and dots' + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + tools: + description: |- + tools provides per-step tools that replace the shared spec.tools + for this step. Use this when different steps need different skills. + minProperties: 1 + properties: + mcpServers: + description: |- + mcpServers defines external MCP (Model Context Protocol) servers the + agent can connect to for additional tools and context. + items: + description: "MCPServerConfig defines the configuration + for an MCP (Model Context Protocol)\nserver that the agent + can connect to for additional tools and context.\nMCP + servers extend the agent's capabilities beyond its built-in + skills.\n\nExample — connecting to an OpenShift MCP server + with SA token auth:\n\n\tmcpServers:\n\t - name: openshift\n\t + \ url: https://mcp.openshift-lightspeed.svc:8443/sse\n\t + \ timeoutSeconds: 10\n\t headers:\n\t - name: + Authorization\n\t valueFrom:\n\t type: + ServiceAccountToken\n\nExample — connecting to an external + API with secret-based auth:\n\n\tmcpServers:\n\t - name: + pagerduty\n\t url: https://mcp-pagerduty.example.com/sse\n\t + \ headers:\n\t - name: X-API-Key\n\t valueFrom:\n\t + \ type: Secret\n\t secret:\n\t name: + pagerduty-api-key" + properties: + headers: + description: headers to send to the MCP server. Maximum + 20 items. + items: + description: |- + MCPHeader defines an HTTP header to send with every request to an + MCP server. Used for authentication and routing. + properties: + name: + description: |- + name of the header (e.g., "Authorization", "X-API-Key"). + Must be at least 1 character, containing only letters, digits, and hyphens. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: name must start with a letter and contain + only letters, digits, and hyphens + rule: self.matches('^[A-Za-z][A-Za-z0-9-]*$') + valueFrom: + description: valueFrom is the source of the header + value. + properties: + secret: + description: |- + secret references a Secret containing the header value. + Required when type is "Secret". + properties: + name: + description: name of the Secret. Must + be a valid RFC 1123 DNS subdomain. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS subdomain: + lowercase alphanumeric characters, + hyphens, and dots' + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + required: + - name + type: object + type: + description: |- + type specifies the source type for the header value. Allowed values: + - "Secret" — reads the value from a Kubernetes Secret (use for + API keys and tokens). Requires the secret field to be set. + - "ServiceAccountToken" — auto-injects a Kubernetes service account token + (for MCP servers that accept K8s auth). + - "Client" — the value is provided by the calling client at + runtime (e.g., forwarded from a user session). + enum: + - Secret + - ServiceAccountToken + - Client + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: secret is required when type is Secret, + and forbidden otherwise + rule: 'self.type == ''Secret'' ? has(self.secret) + : !has(self.secret)' + required: + - name + - valueFrom + type: object + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + name: + description: |- + name of the MCP server. Must start with a letter and contain only + lowercase alphanumeric characters and hyphens. Must be 1-253 characters. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: name must start with a lowercase letter and + contain only lowercase alphanumerics and hyphens + rule: self.matches('^[a-z][a-z0-9-]*$') + timeoutSeconds: + default: 5 + description: |- + timeoutSeconds is the per-request timeout for calls to this MCP server, + in seconds. Default is 5. + Valid range: 1-300. + format: int32 + maximum: 300 + minimum: 1 + type: integer + url: + description: |- + url of the MCP server (HTTP/HTTPS). Must be an HTTP or HTTPS URL, + maximum 2048 characters. + maxLength: 2048 + minLength: 1 + type: string + x-kubernetes-validations: + - message: url must be a valid HTTP or HTTPS URL + rule: isURL(self) && url(self).getScheme() in ['http', + 'https'] + required: + - name + - url + type: object + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + requiredSecrets: + description: |- + requiredSecrets declares Kubernetes Secrets that the sandbox pod + needs at runtime. The cluster admin creates the actual Secrets + in the same namespace as the Proposal. + items: + description: |- + SecretRequirement declares a Kubernetes Secret that the sandbox needs + at runtime. The Secret must exist in the operator namespace (where + sandbox pods run), not in the Proposal's namespace. + properties: + description: + description: |- + description explains what this secret is used for, helping the + cluster admin understand what credentials to provide. + maxLength: 1024 + minLength: 1 + type: string + mountAs: + description: mountAs specifies how the secret is exposed + in the sandbox pod. + properties: + envVar: + description: |- + envVar configures environment variable injection. + Required when type is "EnvVar". + properties: + name: + description: |- + name is the environment variable name (e.g., "GITHUB_TOKEN"). + Must be uppercase letters, digits, and underscores, starting + with a letter or underscore. + maxLength: 256 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid environment variable + name: uppercase letters, digits, and underscores, + starting with a letter or underscore' + rule: self.matches('^[A-Z_][A-Z0-9_]*$') + required: + - name + type: object + filePath: + description: |- + filePath configures file mount. + Required when type is "FilePath". + properties: + path: + description: |- + path is the absolute file path (e.g., "/etc/secrets/tls.crt"). + Must start with a forward slash. + maxLength: 512 + minLength: 2 + type: string + x-kubernetes-validations: + - message: path must be an absolute path starting + with '/' + rule: self.startsWith('/') + required: + - path + type: object + type: + description: |- + type specifies how the secret is exposed. Allowed values: "EnvVar", + "FilePath". + + When set to EnvVar, the secret value is injected as an environment + variable, and the 'envVar' field must be configured. + + When set to FilePath, the secret is mounted as a file, and the + 'filePath' field must be configured. + enum: + - EnvVar + - FilePath + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: envVar is required when type is EnvVar, and + forbidden otherwise + rule: 'self.type == ''EnvVar'' ? has(self.envVar) + : !has(self.envVar)' + - message: filePath is required when type is FilePath, + and forbidden otherwise + rule: 'self.type == ''FilePath'' ? has(self.filePath) + : !has(self.filePath)' + name: + description: |- + name of the Secret (must exist in the operator namespace). + Must be a valid RFC 1123 DNS subdomain. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS subdomain: lowercase + alphanumeric characters, hyphens, and dots' + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + required: + - mountAs + - name + type: object + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + skills: + description: |- + skills defines one or more OCI images containing skills to mount + in the agent's sandbox pod. The operator creates Kubernetes image + volumes (requires K8s 1.34+) and mounts them into the agent's + skills directory. Each image must be unique within the list. + items: + description: "SkillsSource defines an OCI image containing + skills and which paths\nwithin that image to mount. Skills + are mounted as Kubernetes image\nvolumes in the agent's + sandbox pod.\n\nEach path is mounted as a separate subPath + volumeMount, allowing\nselective composition of skills + from shared images.\n\nExample — mount specific skills + from the agentic-skills image:\n\n\tskills:\n\t - image: + registry.ci.openshift.org/ocp/5.0:agentic-skills\n\t paths:\n\t + \ - /skills/cluster-update/update-advisor" + properties: + image: + description: |- + image is the OCI image reference containing skills. + The operator mounts this as a Kubernetes image volume (requires K8s 1.34+). + Must be a valid OCI image pullspec: a domain, followed by a repository path, + ending with either a tag (:tag) or a digest (@algorithm:hex). + Must be 1-512 characters. + maxLength: 512 + minLength: 1 + type: string + x-kubernetes-validations: + - message: must start with a valid domain. valid domains + must be alphanumeric characters (lowercase and uppercase) + separated by the '.' character. + rule: self.matches('^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])((\\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]))+)?(:[0-9]+)?\\b') + - message: a valid name is required. valid names must + contain lowercase alphanumeric characters separated + only by the '.', '_', '__', '-' characters. + rule: self.find('(/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?((/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?)+)?)') + != '' + - message: must end with a digest or a tag + rule: self.find('(@.*:)') != '' || self.find(':.*$') + != '' + - message: tag must not be more than 127 characters + rule: 'self.find(''(@.*:)'') == '''' ? (self.find('':.*$'') + != '''' ? self.find('':.*$'').substring(1).size() + <= 127 : true) : true' + - message: tag is invalid. valid tags must begin with + a word character followed by word characters, '.', + or '-' + rule: 'self.find(''(@.*:)'') == '''' ? (self.find('':.*$'') + != '''' ? self.find('':.*$'').matches('':[\\w][\\w.-]*$'') + : true) : true' + - message: digest algorithm is not valid. valid algorithms + must start with an alpha character followed by alphanumeric + characters and may contain '-', '_', '+', and '.' + characters. + rule: 'self.find(''(@.*:)'') != '''' ? self.find(''(@.*:)'').matches(''(@[A-Za-z][A-Za-z0-9]*([_+.][A-Za-z][A-Za-z0-9]*)*[:])'') + : true' + - message: digest must be at least 32 characters + rule: 'self.find(''(@.*:)'') != '''' ? self.find('':.*$'').substring(1).size() + >= 32 : true' + - message: digest must only contain hex characters (A-F, + a-f, 0-9) + rule: 'self.find(''(@.*:)'') != '''' ? self.find('':.*$'').matches('':[0-9A-Fa-f]*$'') + : true' + paths: + description: |- + paths specifies which directories from the image are mounted. + Each path is mounted as a separate subPath volumeMount into the agent's + skills directory. The last segment of each path becomes the mount name + (e.g., "/skills/prometheus" mounts as "prometheus"). + + Each path must be an absolute file path: starts with "/", no ".." + or "." segments, no double slashes, no trailing slash, and only + alphanumeric characters, hyphens, underscores, dots, and slashes. + + Maximum 50 items. + items: + maxLength: 512 + minLength: 2 + type: string + maxItems: 50 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + x-kubernetes-validations: + - message: each path must be 2-512 characters + rule: self.all(p, p.size() >= 2 && p.size() <= 512) + - message: each path must be absolute (start with '/') + rule: self.all(p, p.startsWith('/')) + - message: paths must not end with '/' + rule: self.all(p, !p.endsWith('/')) + - message: paths must not contain double slashes + rule: self.all(p, !p.contains('//')) + - message: paths must not contain '.' or '..' segments + rule: self.all(p, !p.contains('/../') && !p.endsWith('/..') + && !p.contains('/./') && !p.endsWith('/.')) + - message: paths may only contain alphanumeric characters, + '/', '_', '.', and '-' + rule: self.all(p, p.matches('^[a-zA-Z0-9/_.-]+$')) + required: + - image + - paths + type: object + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - image + x-kubernetes-list-type: map + type: object + type: object + analysisOutput: + description: |- + analysisOutput configures the analysis step's structured output. + The mode field controls which built-in properties are included + (Default: all; Minimal: only title). The schema field optionally + defines adapter-specific structured data injected as "components". + + When omitted, the analysis uses the full default schema with all + built-in properties and no custom components. + + Immutable: the output contract is fixed at creation. + minProperties: 1 + properties: + mode: + default: Default + description: |- + mode controls which built-in properties the analysis output schema + includes. Default includes all built-in properties (diagnosis, + proposal, summary, rbac, verification). Minimal includes only the + base structure (options array with title per option). Omit or set + to "Default" for standard remediation workflows. + enum: + - Default + - Minimal + type: string + schema: + description: |- + schema is a JSON Schema injected as a required "components" + property in each analysis output option. Use this to require + adapter-specific structured data beyond the base analysis schema. + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + x-kubernetes-validations: + - message: schema is required when mode is Minimal + rule: self.mode != 'Minimal' || has(self.schema) + execution: + description: |- + execution defines per-step configuration for the execution step. + Omit to skip execution (advisory/assisted patterns). + + Immutable: agent and per-step tools are fixed at creation. + minProperties: 1 + properties: + agent: + description: |- + agent is the name of the cluster-scoped Agent CR to use for this step. + Defaults to "default" when omitted. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS subdomain: lowercase alphanumeric + characters, hyphens, and dots' + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + tools: + description: |- + tools provides per-step tools that replace the shared spec.tools + for this step. Use this when different steps need different skills. + minProperties: 1 + properties: + mcpServers: + description: |- + mcpServers defines external MCP (Model Context Protocol) servers the + agent can connect to for additional tools and context. + items: + description: "MCPServerConfig defines the configuration + for an MCP (Model Context Protocol)\nserver that the agent + can connect to for additional tools and context.\nMCP + servers extend the agent's capabilities beyond its built-in + skills.\n\nExample — connecting to an OpenShift MCP server + with SA token auth:\n\n\tmcpServers:\n\t - name: openshift\n\t + \ url: https://mcp.openshift-lightspeed.svc:8443/sse\n\t + \ timeoutSeconds: 10\n\t headers:\n\t - name: + Authorization\n\t valueFrom:\n\t type: + ServiceAccountToken\n\nExample — connecting to an external + API with secret-based auth:\n\n\tmcpServers:\n\t - name: + pagerduty\n\t url: https://mcp-pagerduty.example.com/sse\n\t + \ headers:\n\t - name: X-API-Key\n\t valueFrom:\n\t + \ type: Secret\n\t secret:\n\t name: + pagerduty-api-key" + properties: + headers: + description: headers to send to the MCP server. Maximum + 20 items. + items: + description: |- + MCPHeader defines an HTTP header to send with every request to an + MCP server. Used for authentication and routing. + properties: + name: + description: |- + name of the header (e.g., "Authorization", "X-API-Key"). + Must be at least 1 character, containing only letters, digits, and hyphens. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: name must start with a letter and contain + only letters, digits, and hyphens + rule: self.matches('^[A-Za-z][A-Za-z0-9-]*$') + valueFrom: + description: valueFrom is the source of the header + value. + properties: + secret: + description: |- + secret references a Secret containing the header value. + Required when type is "Secret". + properties: + name: + description: name of the Secret. Must + be a valid RFC 1123 DNS subdomain. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS subdomain: + lowercase alphanumeric characters, + hyphens, and dots' + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + required: + - name + type: object + type: + description: |- + type specifies the source type for the header value. Allowed values: + - "Secret" — reads the value from a Kubernetes Secret (use for + API keys and tokens). Requires the secret field to be set. + - "ServiceAccountToken" — auto-injects a Kubernetes service account token + (for MCP servers that accept K8s auth). + - "Client" — the value is provided by the calling client at + runtime (e.g., forwarded from a user session). + enum: + - Secret + - ServiceAccountToken + - Client + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: secret is required when type is Secret, + and forbidden otherwise + rule: 'self.type == ''Secret'' ? has(self.secret) + : !has(self.secret)' + required: + - name + - valueFrom + type: object + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + name: + description: |- + name of the MCP server. Must start with a letter and contain only + lowercase alphanumeric characters and hyphens. Must be 1-253 characters. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: name must start with a lowercase letter and + contain only lowercase alphanumerics and hyphens + rule: self.matches('^[a-z][a-z0-9-]*$') + timeoutSeconds: + default: 5 + description: |- + timeoutSeconds is the per-request timeout for calls to this MCP server, + in seconds. Default is 5. + Valid range: 1-300. + format: int32 + maximum: 300 + minimum: 1 + type: integer + url: + description: |- + url of the MCP server (HTTP/HTTPS). Must be an HTTP or HTTPS URL, + maximum 2048 characters. + maxLength: 2048 + minLength: 1 + type: string + x-kubernetes-validations: + - message: url must be a valid HTTP or HTTPS URL + rule: isURL(self) && url(self).getScheme() in ['http', + 'https'] + required: + - name + - url + type: object + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + requiredSecrets: + description: |- + requiredSecrets declares Kubernetes Secrets that the sandbox pod + needs at runtime. The cluster admin creates the actual Secrets + in the same namespace as the Proposal. + items: + description: |- + SecretRequirement declares a Kubernetes Secret that the sandbox needs + at runtime. The Secret must exist in the operator namespace (where + sandbox pods run), not in the Proposal's namespace. + properties: + description: + description: |- + description explains what this secret is used for, helping the + cluster admin understand what credentials to provide. + maxLength: 1024 + minLength: 1 + type: string + mountAs: + description: mountAs specifies how the secret is exposed + in the sandbox pod. + properties: + envVar: + description: |- + envVar configures environment variable injection. + Required when type is "EnvVar". + properties: + name: + description: |- + name is the environment variable name (e.g., "GITHUB_TOKEN"). + Must be uppercase letters, digits, and underscores, starting + with a letter or underscore. + maxLength: 256 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid environment variable + name: uppercase letters, digits, and underscores, + starting with a letter or underscore' + rule: self.matches('^[A-Z_][A-Z0-9_]*$') + required: + - name + type: object + filePath: + description: |- + filePath configures file mount. + Required when type is "FilePath". + properties: + path: + description: |- + path is the absolute file path (e.g., "/etc/secrets/tls.crt"). + Must start with a forward slash. + maxLength: 512 + minLength: 2 + type: string + x-kubernetes-validations: + - message: path must be an absolute path starting + with '/' + rule: self.startsWith('/') + required: + - path + type: object + type: + description: |- + type specifies how the secret is exposed. Allowed values: "EnvVar", + "FilePath". + + When set to EnvVar, the secret value is injected as an environment + variable, and the 'envVar' field must be configured. + + When set to FilePath, the secret is mounted as a file, and the + 'filePath' field must be configured. + enum: + - EnvVar + - FilePath + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: envVar is required when type is EnvVar, and + forbidden otherwise + rule: 'self.type == ''EnvVar'' ? has(self.envVar) + : !has(self.envVar)' + - message: filePath is required when type is FilePath, + and forbidden otherwise + rule: 'self.type == ''FilePath'' ? has(self.filePath) + : !has(self.filePath)' + name: + description: |- + name of the Secret (must exist in the operator namespace). + Must be a valid RFC 1123 DNS subdomain. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS subdomain: lowercase + alphanumeric characters, hyphens, and dots' + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + required: + - mountAs + - name + type: object + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + skills: + description: |- + skills defines one or more OCI images containing skills to mount + in the agent's sandbox pod. The operator creates Kubernetes image + volumes (requires K8s 1.34+) and mounts them into the agent's + skills directory. Each image must be unique within the list. + items: + description: "SkillsSource defines an OCI image containing + skills and which paths\nwithin that image to mount. Skills + are mounted as Kubernetes image\nvolumes in the agent's + sandbox pod.\n\nEach path is mounted as a separate subPath + volumeMount, allowing\nselective composition of skills + from shared images.\n\nExample — mount specific skills + from the agentic-skills image:\n\n\tskills:\n\t - image: + registry.ci.openshift.org/ocp/5.0:agentic-skills\n\t paths:\n\t + \ - /skills/cluster-update/update-advisor" + properties: + image: + description: |- + image is the OCI image reference containing skills. + The operator mounts this as a Kubernetes image volume (requires K8s 1.34+). + Must be a valid OCI image pullspec: a domain, followed by a repository path, + ending with either a tag (:tag) or a digest (@algorithm:hex). + Must be 1-512 characters. + maxLength: 512 + minLength: 1 + type: string + x-kubernetes-validations: + - message: must start with a valid domain. valid domains + must be alphanumeric characters (lowercase and uppercase) + separated by the '.' character. + rule: self.matches('^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])((\\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]))+)?(:[0-9]+)?\\b') + - message: a valid name is required. valid names must + contain lowercase alphanumeric characters separated + only by the '.', '_', '__', '-' characters. + rule: self.find('(/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?((/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?)+)?)') + != '' + - message: must end with a digest or a tag + rule: self.find('(@.*:)') != '' || self.find(':.*$') + != '' + - message: tag must not be more than 127 characters + rule: 'self.find(''(@.*:)'') == '''' ? (self.find('':.*$'') + != '''' ? self.find('':.*$'').substring(1).size() + <= 127 : true) : true' + - message: tag is invalid. valid tags must begin with + a word character followed by word characters, '.', + or '-' + rule: 'self.find(''(@.*:)'') == '''' ? (self.find('':.*$'') + != '''' ? self.find('':.*$'').matches('':[\\w][\\w.-]*$'') + : true) : true' + - message: digest algorithm is not valid. valid algorithms + must start with an alpha character followed by alphanumeric + characters and may contain '-', '_', '+', and '.' + characters. + rule: 'self.find(''(@.*:)'') != '''' ? self.find(''(@.*:)'').matches(''(@[A-Za-z][A-Za-z0-9]*([_+.][A-Za-z][A-Za-z0-9]*)*[:])'') + : true' + - message: digest must be at least 32 characters + rule: 'self.find(''(@.*:)'') != '''' ? self.find('':.*$'').substring(1).size() + >= 32 : true' + - message: digest must only contain hex characters (A-F, + a-f, 0-9) + rule: 'self.find(''(@.*:)'') != '''' ? self.find('':.*$'').matches('':[0-9A-Fa-f]*$'') + : true' + paths: + description: |- + paths specifies which directories from the image are mounted. + Each path is mounted as a separate subPath volumeMount into the agent's + skills directory. The last segment of each path becomes the mount name + (e.g., "/skills/prometheus" mounts as "prometheus"). + + Each path must be an absolute file path: starts with "/", no ".." + or "." segments, no double slashes, no trailing slash, and only + alphanumeric characters, hyphens, underscores, dots, and slashes. + + Maximum 50 items. + items: + maxLength: 512 + minLength: 2 + type: string + maxItems: 50 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + x-kubernetes-validations: + - message: each path must be 2-512 characters + rule: self.all(p, p.size() >= 2 && p.size() <= 512) + - message: each path must be absolute (start with '/') + rule: self.all(p, p.startsWith('/')) + - message: paths must not end with '/' + rule: self.all(p, !p.endsWith('/')) + - message: paths must not contain double slashes + rule: self.all(p, !p.contains('//')) + - message: paths must not contain '.' or '..' segments + rule: self.all(p, !p.contains('/../') && !p.endsWith('/..') + && !p.contains('/./') && !p.endsWith('/.')) + - message: paths may only contain alphanumeric characters, + '/', '_', '.', and '-' + rule: self.all(p, p.matches('^[a-zA-Z0-9/_.-]+$')) + required: + - image + - paths + type: object + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - image + x-kubernetes-list-type: map + type: object + type: object + request: + description: |- + request is the user's original request, alert description, or a + description of what triggered this proposal. This text is passed to + the analysis agent as the primary input. + + Immutable: Proposals are run-to-completion (like Jobs). To change + the request, create a new Proposal. Use spec.revisionFeedback for + iterative feedback on an existing analysis. + maxLength: 32768 + minLength: 1 + type: string + x-kubernetes-validations: + - message: request is immutable after creation + rule: self == oldSelf + revisionFeedback: + description: |- + revisionFeedback is the user's free-text feedback requesting changes + to the analysis. Patching this field bumps metadata.generation, which + the operator detects (generation > observedGeneration) and triggers + re-analysis with the feedback appended to the original request. + + Mutable: this is the only mutable spec field. All other spec fields + are immutable via CEL rules, so generation changes signal revision. + maxLength: 32768 + minLength: 1 + type: string + targetNamespaces: + description: |- + targetNamespaces are the Kubernetes namespace(s) this proposal + operates on. Used for RBAC scoping and context to the analysis agent. + + When omitted, the proposal is not namespace-scoped — the analysis + agent determines the relevant namespaces from the request context. + Adapters (AlertManager, ACS) typically set this automatically from + the source event. + + Immutable: RBAC scoping is fixed at creation. Changing target + namespaces mid-flight would invalidate the analysis and any + granted execution RBAC. + items: + maxLength: 63 + minLength: 1 + type: string + maxItems: 50 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + x-kubernetes-validations: + - message: each namespace must be a valid DNS label + rule: self.all(ns, !format.dns1123Label().validate(ns).hasValue()) + tools: + description: |- + tools defines the default tools for all steps: skills images, + MCP servers, and required secrets. Per-step tools + (analysis.tools, execution.tools, verification.tools) replace + this default for individual steps. + + Immutable: the skills and secrets available to the agent are + fixed at creation. Changing tools mid-flight could violate the + assumptions of an in-progress analysis or execution. + minProperties: 1 + properties: + mcpServers: + description: |- + mcpServers defines external MCP (Model Context Protocol) servers the + agent can connect to for additional tools and context. + items: + description: "MCPServerConfig defines the configuration for + an MCP (Model Context Protocol)\nserver that the agent can + connect to for additional tools and context.\nMCP servers + extend the agent's capabilities beyond its built-in skills.\n\nExample + — connecting to an OpenShift MCP server with SA token auth:\n\n\tmcpServers:\n\t + \ - name: openshift\n\t url: https://mcp.openshift-lightspeed.svc:8443/sse\n\t + \ timeoutSeconds: 10\n\t headers:\n\t - name: Authorization\n\t + \ valueFrom:\n\t type: ServiceAccountToken\n\nExample + — connecting to an external API with secret-based auth:\n\n\tmcpServers:\n\t + \ - name: pagerduty\n\t url: https://mcp-pagerduty.example.com/sse\n\t + \ headers:\n\t - name: X-API-Key\n\t valueFrom:\n\t + \ type: Secret\n\t secret:\n\t name: + pagerduty-api-key" + properties: + headers: + description: headers to send to the MCP server. Maximum + 20 items. + items: + description: |- + MCPHeader defines an HTTP header to send with every request to an + MCP server. Used for authentication and routing. + properties: + name: + description: |- + name of the header (e.g., "Authorization", "X-API-Key"). + Must be at least 1 character, containing only letters, digits, and hyphens. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: name must start with a letter and contain + only letters, digits, and hyphens + rule: self.matches('^[A-Za-z][A-Za-z0-9-]*$') + valueFrom: + description: valueFrom is the source of the header + value. + properties: + secret: + description: |- + secret references a Secret containing the header value. + Required when type is "Secret". + properties: + name: + description: name of the Secret. Must be a + valid RFC 1123 DNS subdomain. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS subdomain: + lowercase alphanumeric characters, hyphens, + and dots' + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + required: + - name + type: object + type: + description: |- + type specifies the source type for the header value. Allowed values: + - "Secret" — reads the value from a Kubernetes Secret (use for + API keys and tokens). Requires the secret field to be set. + - "ServiceAccountToken" — auto-injects a Kubernetes service account token + (for MCP servers that accept K8s auth). + - "Client" — the value is provided by the calling client at + runtime (e.g., forwarded from a user session). + enum: + - Secret + - ServiceAccountToken + - Client + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: secret is required when type is Secret, + and forbidden otherwise + rule: 'self.type == ''Secret'' ? has(self.secret) + : !has(self.secret)' + required: + - name + - valueFrom + type: object + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + name: + description: |- + name of the MCP server. Must start with a letter and contain only + lowercase alphanumeric characters and hyphens. Must be 1-253 characters. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: name must start with a lowercase letter and contain + only lowercase alphanumerics and hyphens + rule: self.matches('^[a-z][a-z0-9-]*$') + timeoutSeconds: + default: 5 + description: |- + timeoutSeconds is the per-request timeout for calls to this MCP server, + in seconds. Default is 5. + Valid range: 1-300. + format: int32 + maximum: 300 + minimum: 1 + type: integer + url: + description: |- + url of the MCP server (HTTP/HTTPS). Must be an HTTP or HTTPS URL, + maximum 2048 characters. + maxLength: 2048 + minLength: 1 + type: string + x-kubernetes-validations: + - message: url must be a valid HTTP or HTTPS URL + rule: isURL(self) && url(self).getScheme() in ['http', + 'https'] + required: + - name + - url + type: object + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + requiredSecrets: + description: |- + requiredSecrets declares Kubernetes Secrets that the sandbox pod + needs at runtime. The cluster admin creates the actual Secrets + in the same namespace as the Proposal. + items: + description: |- + SecretRequirement declares a Kubernetes Secret that the sandbox needs + at runtime. The Secret must exist in the operator namespace (where + sandbox pods run), not in the Proposal's namespace. + properties: + description: + description: |- + description explains what this secret is used for, helping the + cluster admin understand what credentials to provide. + maxLength: 1024 + minLength: 1 + type: string + mountAs: + description: mountAs specifies how the secret is exposed + in the sandbox pod. + properties: + envVar: + description: |- + envVar configures environment variable injection. + Required when type is "EnvVar". + properties: + name: + description: |- + name is the environment variable name (e.g., "GITHUB_TOKEN"). + Must be uppercase letters, digits, and underscores, starting + with a letter or underscore. + maxLength: 256 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid environment variable + name: uppercase letters, digits, and underscores, + starting with a letter or underscore' + rule: self.matches('^[A-Z_][A-Z0-9_]*$') + required: + - name + type: object + filePath: + description: |- + filePath configures file mount. + Required when type is "FilePath". + properties: + path: + description: |- + path is the absolute file path (e.g., "/etc/secrets/tls.crt"). + Must start with a forward slash. + maxLength: 512 + minLength: 2 + type: string + x-kubernetes-validations: + - message: path must be an absolute path starting + with '/' + rule: self.startsWith('/') + required: + - path + type: object + type: + description: |- + type specifies how the secret is exposed. Allowed values: "EnvVar", + "FilePath". + + When set to EnvVar, the secret value is injected as an environment + variable, and the 'envVar' field must be configured. + + When set to FilePath, the secret is mounted as a file, and the + 'filePath' field must be configured. + enum: + - EnvVar + - FilePath + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: envVar is required when type is EnvVar, and forbidden + otherwise + rule: 'self.type == ''EnvVar'' ? has(self.envVar) : !has(self.envVar)' + - message: filePath is required when type is FilePath, and + forbidden otherwise + rule: 'self.type == ''FilePath'' ? has(self.filePath) + : !has(self.filePath)' + name: + description: |- + name of the Secret (must exist in the operator namespace). + Must be a valid RFC 1123 DNS subdomain. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS subdomain: lowercase alphanumeric + characters, hyphens, and dots' + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + required: + - mountAs + - name + type: object + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + skills: + description: |- + skills defines one or more OCI images containing skills to mount + in the agent's sandbox pod. The operator creates Kubernetes image + volumes (requires K8s 1.34+) and mounts them into the agent's + skills directory. Each image must be unique within the list. + items: + description: "SkillsSource defines an OCI image containing skills + and which paths\nwithin that image to mount. Skills are mounted + as Kubernetes image\nvolumes in the agent's sandbox pod.\n\nEach + path is mounted as a separate subPath volumeMount, allowing\nselective + composition of skills from shared images.\n\nExample — mount + specific skills from the agentic-skills image:\n\n\tskills:\n\t + \ - image: registry.ci.openshift.org/ocp/5.0:agentic-skills\n\t + \ paths:\n\t - /skills/cluster-update/update-advisor" + properties: + image: + description: |- + image is the OCI image reference containing skills. + The operator mounts this as a Kubernetes image volume (requires K8s 1.34+). + Must be a valid OCI image pullspec: a domain, followed by a repository path, + ending with either a tag (:tag) or a digest (@algorithm:hex). + Must be 1-512 characters. + maxLength: 512 + minLength: 1 + type: string + x-kubernetes-validations: + - message: must start with a valid domain. valid domains + must be alphanumeric characters (lowercase and uppercase) + separated by the '.' character. + rule: self.matches('^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])((\\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]))+)?(:[0-9]+)?\\b') + - message: a valid name is required. valid names must contain + lowercase alphanumeric characters separated only by + the '.', '_', '__', '-' characters. + rule: self.find('(/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?((/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?)+)?)') + != '' + - message: must end with a digest or a tag + rule: self.find('(@.*:)') != '' || self.find(':.*$') != + '' + - message: tag must not be more than 127 characters + rule: 'self.find(''(@.*:)'') == '''' ? (self.find('':.*$'') + != '''' ? self.find('':.*$'').substring(1).size() <= + 127 : true) : true' + - message: tag is invalid. valid tags must begin with a + word character followed by word characters, '.', or + '-' + rule: 'self.find(''(@.*:)'') == '''' ? (self.find('':.*$'') + != '''' ? self.find('':.*$'').matches('':[\\w][\\w.-]*$'') + : true) : true' + - message: digest algorithm is not valid. valid algorithms + must start with an alpha character followed by alphanumeric + characters and may contain '-', '_', '+', and '.' characters. + rule: 'self.find(''(@.*:)'') != '''' ? self.find(''(@.*:)'').matches(''(@[A-Za-z][A-Za-z0-9]*([_+.][A-Za-z][A-Za-z0-9]*)*[:])'') + : true' + - message: digest must be at least 32 characters + rule: 'self.find(''(@.*:)'') != '''' ? self.find('':.*$'').substring(1).size() + >= 32 : true' + - message: digest must only contain hex characters (A-F, + a-f, 0-9) + rule: 'self.find(''(@.*:)'') != '''' ? self.find('':.*$'').matches('':[0-9A-Fa-f]*$'') + : true' + paths: + description: |- + paths specifies which directories from the image are mounted. + Each path is mounted as a separate subPath volumeMount into the agent's + skills directory. The last segment of each path becomes the mount name + (e.g., "/skills/prometheus" mounts as "prometheus"). + + Each path must be an absolute file path: starts with "/", no ".." + or "." segments, no double slashes, no trailing slash, and only + alphanumeric characters, hyphens, underscores, dots, and slashes. + + Maximum 50 items. + items: + maxLength: 512 + minLength: 2 + type: string + maxItems: 50 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + x-kubernetes-validations: + - message: each path must be 2-512 characters + rule: self.all(p, p.size() >= 2 && p.size() <= 512) + - message: each path must be absolute (start with '/') + rule: self.all(p, p.startsWith('/')) + - message: paths must not end with '/' + rule: self.all(p, !p.endsWith('/')) + - message: paths must not contain double slashes + rule: self.all(p, !p.contains('//')) + - message: paths must not contain '.' or '..' segments + rule: self.all(p, !p.contains('/../') && !p.endsWith('/..') + && !p.contains('/./') && !p.endsWith('/.')) + - message: paths may only contain alphanumeric characters, + '/', '_', '.', and '-' + rule: self.all(p, p.matches('^[a-zA-Z0-9/_.-]+$')) + required: + - image + - paths + type: object + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - image + x-kubernetes-list-type: map + type: object + verification: + description: |- + verification defines per-step configuration for the verification step. + Omit to skip verification. + + Immutable: agent and per-step tools are fixed at creation. + minProperties: 1 + properties: + agent: + description: |- + agent is the name of the cluster-scoped Agent CR to use for this step. + Defaults to "default" when omitted. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS subdomain: lowercase alphanumeric + characters, hyphens, and dots' + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + tools: + description: |- + tools provides per-step tools that replace the shared spec.tools + for this step. Use this when different steps need different skills. + minProperties: 1 + properties: + mcpServers: + description: |- + mcpServers defines external MCP (Model Context Protocol) servers the + agent can connect to for additional tools and context. + items: + description: "MCPServerConfig defines the configuration + for an MCP (Model Context Protocol)\nserver that the agent + can connect to for additional tools and context.\nMCP + servers extend the agent's capabilities beyond its built-in + skills.\n\nExample — connecting to an OpenShift MCP server + with SA token auth:\n\n\tmcpServers:\n\t - name: openshift\n\t + \ url: https://mcp.openshift-lightspeed.svc:8443/sse\n\t + \ timeoutSeconds: 10\n\t headers:\n\t - name: + Authorization\n\t valueFrom:\n\t type: + ServiceAccountToken\n\nExample — connecting to an external + API with secret-based auth:\n\n\tmcpServers:\n\t - name: + pagerduty\n\t url: https://mcp-pagerduty.example.com/sse\n\t + \ headers:\n\t - name: X-API-Key\n\t valueFrom:\n\t + \ type: Secret\n\t secret:\n\t name: + pagerduty-api-key" + properties: + headers: + description: headers to send to the MCP server. Maximum + 20 items. + items: + description: |- + MCPHeader defines an HTTP header to send with every request to an + MCP server. Used for authentication and routing. + properties: + name: + description: |- + name of the header (e.g., "Authorization", "X-API-Key"). + Must be at least 1 character, containing only letters, digits, and hyphens. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: name must start with a letter and contain + only letters, digits, and hyphens + rule: self.matches('^[A-Za-z][A-Za-z0-9-]*$') + valueFrom: + description: valueFrom is the source of the header + value. + properties: + secret: + description: |- + secret references a Secret containing the header value. + Required when type is "Secret". + properties: + name: + description: name of the Secret. Must + be a valid RFC 1123 DNS subdomain. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS subdomain: + lowercase alphanumeric characters, + hyphens, and dots' + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + required: + - name + type: object + type: + description: |- + type specifies the source type for the header value. Allowed values: + - "Secret" — reads the value from a Kubernetes Secret (use for + API keys and tokens). Requires the secret field to be set. + - "ServiceAccountToken" — auto-injects a Kubernetes service account token + (for MCP servers that accept K8s auth). + - "Client" — the value is provided by the calling client at + runtime (e.g., forwarded from a user session). + enum: + - Secret + - ServiceAccountToken + - Client + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: secret is required when type is Secret, + and forbidden otherwise + rule: 'self.type == ''Secret'' ? has(self.secret) + : !has(self.secret)' + required: + - name + - valueFrom + type: object + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + name: + description: |- + name of the MCP server. Must start with a letter and contain only + lowercase alphanumeric characters and hyphens. Must be 1-253 characters. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: name must start with a lowercase letter and + contain only lowercase alphanumerics and hyphens + rule: self.matches('^[a-z][a-z0-9-]*$') + timeoutSeconds: + default: 5 + description: |- + timeoutSeconds is the per-request timeout for calls to this MCP server, + in seconds. Default is 5. + Valid range: 1-300. + format: int32 + maximum: 300 + minimum: 1 + type: integer + url: + description: |- + url of the MCP server (HTTP/HTTPS). Must be an HTTP or HTTPS URL, + maximum 2048 characters. + maxLength: 2048 + minLength: 1 + type: string + x-kubernetes-validations: + - message: url must be a valid HTTP or HTTPS URL + rule: isURL(self) && url(self).getScheme() in ['http', + 'https'] + required: + - name + - url + type: object + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + requiredSecrets: + description: |- + requiredSecrets declares Kubernetes Secrets that the sandbox pod + needs at runtime. The cluster admin creates the actual Secrets + in the same namespace as the Proposal. + items: + description: |- + SecretRequirement declares a Kubernetes Secret that the sandbox needs + at runtime. The Secret must exist in the operator namespace (where + sandbox pods run), not in the Proposal's namespace. + properties: + description: + description: |- + description explains what this secret is used for, helping the + cluster admin understand what credentials to provide. + maxLength: 1024 + minLength: 1 + type: string + mountAs: + description: mountAs specifies how the secret is exposed + in the sandbox pod. + properties: + envVar: + description: |- + envVar configures environment variable injection. + Required when type is "EnvVar". + properties: + name: + description: |- + name is the environment variable name (e.g., "GITHUB_TOKEN"). + Must be uppercase letters, digits, and underscores, starting + with a letter or underscore. + maxLength: 256 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid environment variable + name: uppercase letters, digits, and underscores, + starting with a letter or underscore' + rule: self.matches('^[A-Z_][A-Z0-9_]*$') + required: + - name + type: object + filePath: + description: |- + filePath configures file mount. + Required when type is "FilePath". + properties: + path: + description: |- + path is the absolute file path (e.g., "/etc/secrets/tls.crt"). + Must start with a forward slash. + maxLength: 512 + minLength: 2 + type: string + x-kubernetes-validations: + - message: path must be an absolute path starting + with '/' + rule: self.startsWith('/') + required: + - path + type: object + type: + description: |- + type specifies how the secret is exposed. Allowed values: "EnvVar", + "FilePath". + + When set to EnvVar, the secret value is injected as an environment + variable, and the 'envVar' field must be configured. + + When set to FilePath, the secret is mounted as a file, and the + 'filePath' field must be configured. + enum: + - EnvVar + - FilePath + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: envVar is required when type is EnvVar, and + forbidden otherwise + rule: 'self.type == ''EnvVar'' ? has(self.envVar) + : !has(self.envVar)' + - message: filePath is required when type is FilePath, + and forbidden otherwise + rule: 'self.type == ''FilePath'' ? has(self.filePath) + : !has(self.filePath)' + name: + description: |- + name of the Secret (must exist in the operator namespace). + Must be a valid RFC 1123 DNS subdomain. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS subdomain: lowercase + alphanumeric characters, hyphens, and dots' + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + required: + - mountAs + - name + type: object + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + skills: + description: |- + skills defines one or more OCI images containing skills to mount + in the agent's sandbox pod. The operator creates Kubernetes image + volumes (requires K8s 1.34+) and mounts them into the agent's + skills directory. Each image must be unique within the list. + items: + description: "SkillsSource defines an OCI image containing + skills and which paths\nwithin that image to mount. Skills + are mounted as Kubernetes image\nvolumes in the agent's + sandbox pod.\n\nEach path is mounted as a separate subPath + volumeMount, allowing\nselective composition of skills + from shared images.\n\nExample — mount specific skills + from the agentic-skills image:\n\n\tskills:\n\t - image: + registry.ci.openshift.org/ocp/5.0:agentic-skills\n\t paths:\n\t + \ - /skills/cluster-update/update-advisor" + properties: + image: + description: |- + image is the OCI image reference containing skills. + The operator mounts this as a Kubernetes image volume (requires K8s 1.34+). + Must be a valid OCI image pullspec: a domain, followed by a repository path, + ending with either a tag (:tag) or a digest (@algorithm:hex). + Must be 1-512 characters. + maxLength: 512 + minLength: 1 + type: string + x-kubernetes-validations: + - message: must start with a valid domain. valid domains + must be alphanumeric characters (lowercase and uppercase) + separated by the '.' character. + rule: self.matches('^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])((\\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]))+)?(:[0-9]+)?\\b') + - message: a valid name is required. valid names must + contain lowercase alphanumeric characters separated + only by the '.', '_', '__', '-' characters. + rule: self.find('(/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?((/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?)+)?)') + != '' + - message: must end with a digest or a tag + rule: self.find('(@.*:)') != '' || self.find(':.*$') + != '' + - message: tag must not be more than 127 characters + rule: 'self.find(''(@.*:)'') == '''' ? (self.find('':.*$'') + != '''' ? self.find('':.*$'').substring(1).size() + <= 127 : true) : true' + - message: tag is invalid. valid tags must begin with + a word character followed by word characters, '.', + or '-' + rule: 'self.find(''(@.*:)'') == '''' ? (self.find('':.*$'') + != '''' ? self.find('':.*$'').matches('':[\\w][\\w.-]*$'') + : true) : true' + - message: digest algorithm is not valid. valid algorithms + must start with an alpha character followed by alphanumeric + characters and may contain '-', '_', '+', and '.' + characters. + rule: 'self.find(''(@.*:)'') != '''' ? self.find(''(@.*:)'').matches(''(@[A-Za-z][A-Za-z0-9]*([_+.][A-Za-z][A-Za-z0-9]*)*[:])'') + : true' + - message: digest must be at least 32 characters + rule: 'self.find(''(@.*:)'') != '''' ? self.find('':.*$'').substring(1).size() + >= 32 : true' + - message: digest must only contain hex characters (A-F, + a-f, 0-9) + rule: 'self.find(''(@.*:)'') != '''' ? self.find('':.*$'').matches('':[0-9A-Fa-f]*$'') + : true' + paths: + description: |- + paths specifies which directories from the image are mounted. + Each path is mounted as a separate subPath volumeMount into the agent's + skills directory. The last segment of each path becomes the mount name + (e.g., "/skills/prometheus" mounts as "prometheus"). + + Each path must be an absolute file path: starts with "/", no ".." + or "." segments, no double slashes, no trailing slash, and only + alphanumeric characters, hyphens, underscores, dots, and slashes. + + Maximum 50 items. + items: + maxLength: 512 + minLength: 2 + type: string + maxItems: 50 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + x-kubernetes-validations: + - message: each path must be 2-512 characters + rule: self.all(p, p.size() >= 2 && p.size() <= 512) + - message: each path must be absolute (start with '/') + rule: self.all(p, p.startsWith('/')) + - message: paths must not end with '/' + rule: self.all(p, !p.endsWith('/')) + - message: paths must not contain double slashes + rule: self.all(p, !p.contains('//')) + - message: paths must not contain '.' or '..' segments + rule: self.all(p, !p.contains('/../') && !p.endsWith('/..') + && !p.contains('/./') && !p.endsWith('/.')) + - message: paths may only contain alphanumeric characters, + '/', '_', '.', and '-' + rule: self.all(p, p.matches('^[a-zA-Z0-9/_.-]+$')) + required: + - image + - paths + type: object + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - image + x-kubernetes-list-type: map + type: object + type: object + required: + - analysis + - request + type: object + x-kubernetes-validations: + - message: analysis must be provided + rule: has(self.analysis) + - message: targetNamespaces is immutable once set + rule: '!has(oldSelf.targetNamespaces) || (has(self.targetNamespaces) + && self.targetNamespaces == oldSelf.targetNamespaces)' + - message: analysisOutput is immutable once set + rule: '!has(oldSelf.analysisOutput) || (has(self.analysisOutput) && + self.analysisOutput == oldSelf.analysisOutput)' + - message: analysisOutput mode Minimal is only allowed for analysis-only + proposals (no execution or verification steps) + rule: '!has(self.analysisOutput) || self.analysisOutput.mode != ''Minimal'' + || (!has(self.execution) && !has(self.verification))' + - message: tools is immutable once set + rule: '!has(oldSelf.tools) || (has(self.tools) && self.tools == oldSelf.tools)' + - message: analysis is immutable once set + rule: '!has(oldSelf.analysis) || (has(self.analysis) && self.analysis + == oldSelf.analysis)' + - message: execution is immutable once set + rule: '!has(oldSelf.execution) || (has(self.execution) && self.execution + == oldSelf.execution)' + - message: verification is immutable once set + rule: '!has(oldSelf.verification) || (has(self.verification) && self.verification + == oldSelf.verification)' + status: + description: status defines the observed state of Proposal. + minProperties: 1 + properties: + conditions: + description: |- + conditions represent the latest available observations using the + standard Kubernetes condition pattern. Condition types include: + Analyzed, Approved, Executed, Verified, and Escalated. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + steps: + description: |- + steps contains the per-step observed state (analysis, execution, + verification). Each step independently tracks its timing, sandbox + info, and references to result CRs. + minProperties: 1 + properties: + analysis: + description: analysis is the observed state of the analysis step. + minProperties: 1 + properties: + conditions: + description: conditions for this step. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + results: + description: |- + results references AnalysisResult CRs, newest last. + Each entry corresponds to one analysis attempt. + items: + description: |- + StepResultRef is a lightweight reference to a result CR with an inline + success field for quick scanning without fetching the CR. + properties: + name: + description: name is the name of the result CR. + maxLength: 253 + minLength: 1 + type: string + outcome: + description: |- + outcome indicates the result of this step attempt. + Must be one of: Succeeded, Failed. + enum: + - Succeeded + - Failed + type: string + required: + - name + - outcome + type: object + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + sandbox: + description: sandbox tracks the sandbox used. + properties: + claimName: + description: |- + claimName is the name of the SandboxClaim resource that owns the + sandbox pod. Maximum 253 characters. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + namespace is the namespace where the SandboxClaim and its pod live. + Must be a valid RFC 1123 DNS label. + maxLength: 63 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS label: lowercase alphanumeric + characters and hyphens, starting with an alphabetic + character and ending with an alphanumeric character' + rule: '!format.dns1123Label().validate(self).hasValue()' + required: + - claimName + - namespace + type: object + type: object + escalation: + description: escalation is the observed state of the escalation + step. + minProperties: 1 + properties: + conditions: + description: conditions for this step. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + results: + description: results references EscalationResult CRs, newest + last. + items: + description: |- + StepResultRef is a lightweight reference to a result CR with an inline + success field for quick scanning without fetching the CR. + properties: + name: + description: name is the name of the result CR. + maxLength: 253 + minLength: 1 + type: string + outcome: + description: |- + outcome indicates the result of this step attempt. + Must be one of: Succeeded, Failed. + enum: + - Succeeded + - Failed + type: string + required: + - name + - outcome + type: object + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + sandbox: + description: sandbox tracks the sandbox used. + properties: + claimName: + description: |- + claimName is the name of the SandboxClaim resource that owns the + sandbox pod. Maximum 253 characters. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + namespace is the namespace where the SandboxClaim and its pod live. + Must be a valid RFC 1123 DNS label. + maxLength: 63 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS label: lowercase alphanumeric + characters and hyphens, starting with an alphabetic + character and ending with an alphanumeric character' + rule: '!format.dns1123Label().validate(self).hasValue()' + required: + - claimName + - namespace + type: object + type: object + execution: + description: execution is the observed state of the execution + step. + minProperties: 1 + properties: + conditions: + description: conditions for this step. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + results: + description: |- + results references ExecutionResult CRs, newest last. + Each entry corresponds to one execution attempt (including retries). + items: + description: |- + StepResultRef is a lightweight reference to a result CR with an inline + success field for quick scanning without fetching the CR. + properties: + name: + description: name is the name of the result CR. + maxLength: 253 + minLength: 1 + type: string + outcome: + description: |- + outcome indicates the result of this step attempt. + Must be one of: Succeeded, Failed. + enum: + - Succeeded + - Failed + type: string + required: + - name + - outcome + type: object + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + retryCount: + description: |- + retryCount tracks how many times execution+verification has been + retried for the current analysis option. Reset when a new analysis + is run (initial or revision). The operator increments this on each + objective verification failure before retrying execution. + format: int32 + minimum: 0 + type: integer + sandbox: + description: sandbox tracks the sandbox used. + properties: + claimName: + description: |- + claimName is the name of the SandboxClaim resource that owns the + sandbox pod. Maximum 253 characters. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + namespace is the namespace where the SandboxClaim and its pod live. + Must be a valid RFC 1123 DNS label. + maxLength: 63 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS label: lowercase alphanumeric + characters and hyphens, starting with an alphabetic + character and ending with an alphanumeric character' + rule: '!format.dns1123Label().validate(self).hasValue()' + required: + - claimName + - namespace + type: object + type: object + verification: + description: verification is the observed state of the verification + step. + minProperties: 1 + properties: + conditions: + description: conditions for this step. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + results: + description: |- + results references VerificationResult CRs, newest last. + Each entry corresponds to one verification attempt (including retries). + items: + description: |- + StepResultRef is a lightweight reference to a result CR with an inline + success field for quick scanning without fetching the CR. + properties: + name: + description: name is the name of the result CR. + maxLength: 253 + minLength: 1 + type: string + outcome: + description: |- + outcome indicates the result of this step attempt. + Must be one of: Succeeded, Failed. + enum: + - Succeeded + - Failed + type: string + required: + - name + - outcome + type: object + maxItems: 20 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + sandbox: + description: sandbox tracks the sandbox used. + properties: + claimName: + description: |- + claimName is the name of the SandboxClaim resource that owns the + sandbox pod. Maximum 253 characters. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + namespace is the namespace where the SandboxClaim and its pod live. + Must be a valid RFC 1123 DNS label. + maxLength: 63 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS label: lowercase alphanumeric + characters and hyphens, starting with an alphabetic + character and ending with an alphanumeric character' + rule: '!format.dns1123Label().validate(self).hasValue()' + required: + - claimName + - namespace + type: object + type: object + type: object + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/agentic.openshift.io_verificationresults.yaml b/config/crd/bases/agentic.openshift.io_verificationresults.yaml new file mode 100644 index 000000000..af892bdd6 --- /dev/null +++ b/config/crd/bases/agentic.openshift.io_verificationresults.yaml @@ -0,0 +1,235 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + name: verificationresults.agentic.openshift.io +spec: + group: agentic.openshift.io + names: + kind: VerificationResult + listKind: VerificationResultList + plural: verificationresults + singular: verificationresult + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.proposalName + name: Proposal + type: string + - jsonPath: .spec.retryIndex + name: Retry + type: integer + - jsonPath: .status.conditions[?(@.type=="Completed")].reason + name: Outcome + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + VerificationResult records the output of a single verification step + execution. Created by the operator after the verification agent + completes. Owned by the parent Proposal for garbage collection. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec contains the immutable identity fields for this result. + properties: + proposalName: + description: proposalName is the name of the parent Proposal in the + same namespace. + maxLength: 253 + minLength: 1 + type: string + retryIndex: + description: retryIndex is the 0-based retry index within the current + analysis. + format: int32 + maximum: 2 + minimum: 0 + type: integer + required: + - proposalName + - retryIndex + type: object + x-kubernetes-validations: + - message: spec is immutable + rule: self == oldSelf + status: + description: status contains result data and conditions. + minProperties: 1 + properties: + checks: + description: checks contains individual verification check results. + items: + description: |- + VerifyCheck is a single verification check result from the verification + agent. Each check corresponds to a VerificationStep from the analysis + agent's verification plan. + properties: + name: + description: |- + name is the check identifier, matching the VerificationStep name. + Maximum 253 characters. + maxLength: 253 + minLength: 1 + type: string + result: + description: |- + result indicates whether the check's observed value matches + the expected value. Must be one of: Passed, Failed. + enum: + - Passed + - Failed + type: string + source: + description: |- + source is what performed the check (e.g., "oc", "promql", "curl"). + Maximum 256 characters. + maxLength: 256 + minLength: 1 + type: string + value: + description: |- + value is the actual observed value (e.g., "Running", "3 replicas"). + Maximum 4096 characters. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - result + - source + - value + type: object + maxItems: 50 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + conditions: + description: conditions track the lifecycle of this result. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + failureReason: + description: failureReason is populated when the step failed due to + a system error. + maxLength: 8192 + minLength: 1 + type: string + sandbox: + description: sandbox tracks the sandbox pod used for this verification. + properties: + claimName: + description: |- + claimName is the name of the SandboxClaim resource that owns the + sandbox pod. Maximum 253 characters. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + namespace is the namespace where the SandboxClaim and its pod live. + Must be a valid RFC 1123 DNS label. + maxLength: 63 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'must be a valid DNS label: lowercase alphanumeric + characters and hyphens, starting with an alphabetic character + and ending with an alphanumeric character' + rule: '!format.dns1123Label().validate(self).hasValue()' + required: + - claimName + - namespace + type: object + summary: + description: summary is a Markdown-formatted verification summary. + maxLength: 32768 + minLength: 1 + type: string + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index f9198651e..372388f81 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -3,6 +3,15 @@ # It should be run by config/default resources: - bases/ols.openshift.io_olsconfigs.yaml +- bases/agentic.openshift.io_agents.yaml +- bases/agentic.openshift.io_analysisresults.yaml +- bases/agentic.openshift.io_approvalpolicies.yaml +- bases/agentic.openshift.io_escalationresults.yaml +- bases/agentic.openshift.io_executionresults.yaml +- bases/agentic.openshift.io_llmproviders.yaml +- bases/agentic.openshift.io_proposalapprovals.yaml +- bases/agentic.openshift.io_proposals.yaml +- bases/agentic.openshift.io_verificationresults.yaml #+kubebuilder:scaffold:crdkustomizeresource patches: diff --git a/config/manifests/bases/lightspeed-operator.clusterserviceversion.yaml b/config/manifests/bases/lightspeed-operator.clusterserviceversion.yaml index 0a569dcd0..f131318bc 100644 --- a/config/manifests/bases/lightspeed-operator.clusterserviceversion.yaml +++ b/config/manifests/bases/lightspeed-operator.clusterserviceversion.yaml @@ -361,6 +361,7 @@ spec: - tls.key: Private key (PEM format) - REQUIRED - ca.crt: CA certificate for console proxy trust (PEM format) - OPTIONAL + If ca.crt is not provided, the OpenShift Console proxy will use the default system trust store. displayName: TLS Certificate Secret Reference path: ols.tlsConfig.keyCertSecretRef diff --git a/config/samples/agentic_v1alpha1_agent.yaml b/config/samples/agentic_v1alpha1_agent.yaml new file mode 100644 index 000000000..2aa67f893 --- /dev/null +++ b/config/samples/agentic_v1alpha1_agent.yaml @@ -0,0 +1,9 @@ +apiVersion: agentic.openshift.io/v1alpha1 +kind: Agent +metadata: + name: default +spec: + llmProvider: + name: vertex-ai + model: claude-opus-4-6 + maxTurns: 200 diff --git a/config/samples/agentic_v1alpha1_analysisresult.yaml b/config/samples/agentic_v1alpha1_analysisresult.yaml new file mode 100644 index 000000000..3c5605afd --- /dev/null +++ b/config/samples/agentic_v1alpha1_analysisresult.yaml @@ -0,0 +1,7 @@ +apiVersion: agentic.openshift.io/v1alpha1 +kind: AnalysisResult +metadata: + name: fix-crashloop + namespace: openshift-lightspeed +spec: + proposalName: fix-crashloop diff --git a/config/samples/agentic_v1alpha1_approvalpolicy.yaml b/config/samples/agentic_v1alpha1_approvalpolicy.yaml new file mode 100644 index 000000000..404357cd4 --- /dev/null +++ b/config/samples/agentic_v1alpha1_approvalpolicy.yaml @@ -0,0 +1,14 @@ +apiVersion: agentic.openshift.io/v1alpha1 +kind: ApprovalPolicy +metadata: + name: cluster +spec: + maxAttempts: 3 + maxConcurrentProposals: 5 + stages: + - name: Analysis + approval: Manual + - name: Execution + approval: Manual + - name: Verification + approval: Manual diff --git a/config/samples/agentic_v1alpha1_escalationresult.yaml b/config/samples/agentic_v1alpha1_escalationresult.yaml new file mode 100644 index 000000000..6c4c865c8 --- /dev/null +++ b/config/samples/agentic_v1alpha1_escalationresult.yaml @@ -0,0 +1,7 @@ +apiVersion: agentic.openshift.io/v1alpha1 +kind: EscalationResult +metadata: + name: fix-crashloop + namespace: openshift-lightspeed +spec: + proposalName: fix-crashloop diff --git a/config/samples/agentic_v1alpha1_executionresult.yaml b/config/samples/agentic_v1alpha1_executionresult.yaml new file mode 100644 index 000000000..671a7d076 --- /dev/null +++ b/config/samples/agentic_v1alpha1_executionresult.yaml @@ -0,0 +1,8 @@ +apiVersion: agentic.openshift.io/v1alpha1 +kind: ExecutionResult +metadata: + name: fix-crashloop-0 + namespace: openshift-lightspeed +spec: + proposalName: fix-crashloop + retryIndex: 0 diff --git a/config/samples/agentic_v1alpha1_llmprovider.yaml b/config/samples/agentic_v1alpha1_llmprovider.yaml new file mode 100644 index 000000000..e9dc49507 --- /dev/null +++ b/config/samples/agentic_v1alpha1_llmprovider.yaml @@ -0,0 +1,12 @@ +apiVersion: agentic.openshift.io/v1alpha1 +kind: LLMProvider +metadata: + name: vertex-ai +spec: + type: GoogleCloudVertex + googleCloudVertex: + credentialsSecret: + name: llm-credentials + projectID: my-gcp-project + region: us-central1 + modelProvider: Anthropic diff --git a/config/samples/agentic_v1alpha1_proposal.yaml b/config/samples/agentic_v1alpha1_proposal.yaml new file mode 100644 index 000000000..56767a9db --- /dev/null +++ b/config/samples/agentic_v1alpha1_proposal.yaml @@ -0,0 +1,15 @@ +apiVersion: agentic.openshift.io/v1alpha1 +kind: Proposal +metadata: + name: fix-crashloop + namespace: openshift-lightspeed +spec: + request: "Pod api-server-xyz is crash looping in production namespace" + targetNamespaces: + - production + analysis: + agent: smart + execution: + agent: default + verification: + agent: fast diff --git a/config/samples/agentic_v1alpha1_proposalapproval.yaml b/config/samples/agentic_v1alpha1_proposalapproval.yaml new file mode 100644 index 000000000..24aad5e5f --- /dev/null +++ b/config/samples/agentic_v1alpha1_proposalapproval.yaml @@ -0,0 +1,12 @@ +apiVersion: agentic.openshift.io/v1alpha1 +kind: ProposalApproval +metadata: + name: fix-crashloop + namespace: openshift-lightspeed +spec: + stages: + - type: Analysis + analysis: {} + - type: Execution + execution: + option: 0 diff --git a/config/samples/agentic_v1alpha1_verificationresult.yaml b/config/samples/agentic_v1alpha1_verificationresult.yaml new file mode 100644 index 000000000..e1e48eed6 --- /dev/null +++ b/config/samples/agentic_v1alpha1_verificationresult.yaml @@ -0,0 +1,8 @@ +apiVersion: agentic.openshift.io/v1alpha1 +kind: VerificationResult +metadata: + name: fix-crashloop-0 + namespace: openshift-lightspeed +spec: + proposalName: fix-crashloop + retryIndex: 0 diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml index a272b61ae..1e5ae78d4 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -1,4 +1,13 @@ ## Append samples of your project ## resources: - ols_v1alpha1_olsconfig.yaml +- agentic_v1alpha1_agent.yaml +- agentic_v1alpha1_analysisresult.yaml +- agentic_v1alpha1_approvalpolicy.yaml +- agentic_v1alpha1_escalationresult.yaml +- agentic_v1alpha1_executionresult.yaml +- agentic_v1alpha1_llmprovider.yaml +- agentic_v1alpha1_proposalapproval.yaml +- agentic_v1alpha1_proposal.yaml +- agentic_v1alpha1_verificationresult.yaml #+kubebuilder:scaffold:manifestskustomizesamples diff --git a/hack/sync_agentic_crds.sh b/hack/sync_agentic_crds.sh new file mode 100755 index 000000000..e80d70127 --- /dev/null +++ b/hack/sync_agentic_crds.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +set -euo pipefail + +REPO="${1:?Usage: $0 }" +REF="${2:?Usage: $0 }" +DEST="${3:?Usage: $0 }" + +TMPDIR=$(mktemp -d) +trap 'rm -rf "$TMPDIR"' EXIT + +echo "Syncing agentic CRDs from ${REPO} at ref ${REF}..." +git init "${TMPDIR}" --quiet +git -C "${TMPDIR}" remote add origin "${REPO}" +git -C "${TMPDIR}" fetch --depth 1 origin "${REF}" --quiet +git -C "${TMPDIR}" checkout FETCH_HEAD --quiet + +SRC="${TMPDIR}/config/crd/bases" +if [ ! -d "${SRC}" ]; then + echo "ERROR: ${SRC} not found in cloned repo" >&2 + exit 1 +fi + +mkdir -p "${DEST}" +rm -f "${DEST}"/agentic.openshift.io_*.yaml + +count=0 +for f in "${SRC}"/agentic.openshift.io_*.yaml; do + [ -f "$f" ] || continue + cp "$f" "${DEST}/" + echo " $(basename "$f")" + count=$((count + 1)) +done + +if [ "${count}" -eq 0 ]; then + echo "WARNING: no agentic CRD files found" >&2 + exit 1 +fi + +KUSTOMIZATION="config/crd/kustomization.yaml" +if [ -f "${KUSTOMIZATION}" ]; then + sed -i '/^- bases\/agentic\.openshift\.io_/d' "${KUSTOMIZATION}" + SCAFFOLD_LINE="#+kubebuilder:scaffold:crdkustomizeresource" + for f in "${SRC}"/agentic.openshift.io_*.yaml; do + [ -f "$f" ] || continue + ENTRY="- bases/$(basename "$f")" + sed -i "s|${SCAFFOLD_LINE}|${ENTRY}\n${SCAFFOLD_LINE}|" "${KUSTOMIZATION}" + done + echo "Updated ${KUSTOMIZATION}" +fi + +SAMPLES_SRC="${TMPDIR}/config/samples" +SAMPLES_DEST="config/samples" +SAMPLES_KUSTOMIZATION="${SAMPLES_DEST}/kustomization.yaml" +if [ -d "${SAMPLES_SRC}" ]; then + sed -i '/^- agentic_/d' "${SAMPLES_KUSTOMIZATION}" 2>/dev/null || true + SAMPLES_SCAFFOLD="#+kubebuilder:scaffold:manifestskustomizesamples" + sample_count=0 + for f in "${SAMPLES_SRC}"/agentic_*.yaml; do + [ -f "$f" ] || continue + cp "$f" "${SAMPLES_DEST}/" + ENTRY="- $(basename "$f")" + sed -i "s|${SAMPLES_SCAFFOLD}|${ENTRY}\n${SAMPLES_SCAFFOLD}|" "${SAMPLES_KUSTOMIZATION}" + echo " $(basename "$f")" + sample_count=$((sample_count + 1)) + done + if [ "${sample_count}" -gt 0 ]; then + echo "Updated ${SAMPLES_KUSTOMIZATION}" + echo "Synced ${sample_count} agentic sample files to ${SAMPLES_DEST}/" + fi +fi + +echo "Synced ${count} agentic CRD files to ${DEST}/"