Repository: kubernetes-sigs/kro

Issue:
Disallow creation of ResourceGroups if value of immutable field is invalid
### Description

<!-- Please craft a detailed description of the documentation issue or suggestion -->
<!-- Below are some suggestions but feel free to modify or remove them -->

**Observed Behavior**:
kro currently allows a ResourceGroup with an invalid value of an immutable field to be created.
This then requires the deletion and recreation of the ResourceGroup.

**Expected Behavior**:
The kro controller returns an error and doesn't create the ResourceGroup.

**Reproduction Steps** (Please include `ResourceGroup` and `Instances` files):

```
apiVersion: kro.run/v1alpha1
kind: ResourceGroup
metadata:
  name: sqs-queue
spec:
  schema:
    apiVersion: v1alpha1
    kind: sqs-queue
    spec:
      ...
```

**Versions**:
- kro version:
- Kubernetes Version (`kubectl version`):

**Involved Controllers**:
- Controller URLs and Versions (if applicable):
- kro v0.1.0

**Error Logs** (if applicable)**:
```
Paste any relevant logs here
```

* Please vote on this issue by adding a 👍 [reaction](https://blog.github.com/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/) to the original issue to help the community and maintainers prioritize this request
* Please do not leave "+1" or "me too" comments, they generate extra noise for issue followers and do not help prioritize the request
* If you are interested in working on this issue or have submitted a pull request, please leave a comment


### Which option describes the most your issue?

_No response_

Hints from issue comments:
@RinkiyaKeDad: Hey @amirahav! Thanks for reporting this issue. I'd like to pick this up. 

I was thinking about the checks we need to do, are these two the only cases? 

- check if `kind` is capitalized and has no dashes or spaces
- check (via regex) if `apiVersion` follows the pattern of `vX`



Solution patch:
diff --git a/.github/workflows/build-push-image.yaml b/.github/workflows/build-push-image.yaml
index 69cdc91..4fa4d3c 100644
--- a/.github/workflows/build-push-image.yaml
+++ b/.github/workflows/build-push-image.yaml
@@ -4,8 +4,16 @@ on:
   push:
     branches: [ main ]
     tags: [ 'v*' ]
+    paths:
+      - "**.go"
+      - go.mod
+      - go.sum
   pull_request:
     branches: [ main ]
+    paths:
+      - "**.go"
+      - go.mod
+      - go.sum
 
 env:
   KO_DOCKER_REPO: ghcr.io/${{ github.repository }}/controller
diff --git a/.github/workflows/golangci-lint.yaml b/.github/workflows/golangci-lint.yaml
index 9fb8a02..9f8c358 100644
--- a/.github/workflows/golangci-lint.yaml
+++ b/.github/workflows/golangci-lint.yaml
@@ -3,9 +3,13 @@ on:
   push:
     branches:
       - main
+    paths:
+      - "**.go"
   pull_request:
     branches:
       - main
+    paths:
+      - "**.go"
 
 permissions:
   contents: read
diff --git a/.github/workflows/integration-tests.yaml b/.github/workflows/integration-tests.yaml
index 4c31242..0430758 100644
--- a/.github/workflows/integration-tests.yaml
+++ b/.github/workflows/integration-tests.yaml
@@ -3,8 +3,16 @@ name: Integration Tests
 on:
   push:
     branches: [ main ]
+    paths:
+      - "**.go"
+      - go.mod
+      - go.sum
   pull_request:
     branches: [ main ]
+    paths:
+      - "**.go"
+      - go.mod
+      - go.sum
 
 jobs:
   integration:
diff --git a/.github/workflows/unit-tests.yaml b/.github/workflows/unit-tests.yaml
index 90dfb08..a91b542 100644
--- a/.github/workflows/unit-tests.yaml
+++ b/.github/workflows/unit-tests.yaml
@@ -2,8 +2,16 @@ name: unit tests
 on:
   push:
     branches: [ main ]
+    paths:
+      - "**.go"
+      - go.mod
+      - go.sum
   pull_request:
     branches: [ main ]
+    paths:
+      - "**.go"
+      - go.mod
+      - go.sum
 
 jobs:
   build:
diff --git a/.ko.yaml b/.ko.yaml
index 2d850e3..d8d6e17 100644
--- a/.ko.yaml
+++ b/.ko.yaml
@@ -5,3 +5,6 @@ defaultPlatforms:
 defaultLdflags:
   - -s
   - -w
+  - -X github.com/kro-run/kro/pkg.Version={{.Env.RELEASE_VERSION}}
+  - -X github.com/kro-run/kro/pkg.GitCommit={{.Env.GIT_COMMIT}}
+  - -X github.com/kro-run/kro/pkg.BuildDate={{.Env.BUILD_DATE}}
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index dea0ddb..1aa3370 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -3,8 +3,22 @@
 Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional
 documentation, we greatly value feedback and contributions from our community.
 
-Please read through this document before submitting any issues or pull requests to ensure we have all the necessary
-information to effectively respond to your bug report or contribution.
+Please read through this document before submitting any issues or pull requests to ensure we have all the necessary information to effectively respond to your bug report or contribution.
+
+## Getting Started
+
+If you're looking to join this wonderful wave of collaboration, here's how to begin:
+
+1. **Look at open issues** to see what needs attention. 
+   - If you want to contribute a feature that does not have an open issue, please open an issue before starting work on it. This could save you from working on something outside the scope of the project.
+
+2. **For an unassigned issue:**
+   - Comment on the issue before you start working on to prevent duplicate work.
+
+3. **If there's an issue you're interested in that's already assigned:**
+   - If there haven't been recent updates, politely comment asking about the status
+       - Example: "Hi! I notice this issue hasn't had recent activity. @assignee, are you still working on this? If not, would it be okay if I took it over?"
+   - **Begin your contribution** once the issue is assigned to you.
 
 ## Reporting Bugs/Feature Requests
 
@@ -18,13 +32,13 @@ reported the issue. Please try to include as much information as you can. Detail
 * Any modifications you've made relevant to the bug
 * Anything unusual about your environment or deployment
 
-
 ## Contributing via Pull Requests
+
 Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that:
 
 1. You are working against the latest source on the *main* branch.
 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already.
-3. You open an issue to discuss any significant work - we would hate for your time to be wasted.
+3. You open an issue to discuss any code more than a small bugfix - we would hate for your time to be wasted.
 
 To send us a pull request, please:
 
@@ -38,24 +52,22 @@ To send us a pull request, please:
 GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and
 [creating a pull request](https://help.github.com/articles/creating-a-pull-request/).
 
-## Development setup
+## Development Setup
 
-Setup the [local enviroment](docs/developer-getting-started.md) to build and test the code locally.
+Setup the [local environment](docs/developer-getting-started.md) to build and test the code locally.
 
-## Finding contributions to work on
-Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start.
+## Finding Contributions to Work On
 
+Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start.
 
 ## Code of Conduct
 
 This project has adopted the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/main/code-of-conduct.md).
 
-
-## Security 
+## Security
 
 TODO
 
-
 ## Licensing
 
-See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution.
+See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution.
\ No newline at end of file
diff --git a/Makefile b/Makefile
index b87899b..9580572 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,6 @@
-
-RELEASE_VERSION ?= dev-$(shell git rev-parse --short HEAD)
+export GIT_COMMIT ?= $(shell git rev-parse --short HEAD)
+export BUILD_DATE ?= $(shell date -u +'%Y-%m-%dT%H:%M:%SZ')
+export RELEASE_VERSION ?= dev-$(GIT_COMMIT)
 OCI_REPO ?= ghcr.io/kro-run/kro
 
 HELM_IMAGE ?= ${OCI_REPO}
@@ -9,6 +10,10 @@ KOCACHE ?= ~/.ko
 KO_PUSH ?= true
 export KIND_CLUSTER_NAME ?= kro
 
+LDFLAGS ?= -X github.com/kro-run/kro/pkg.Version=$(RELEASE_VERSION) \
+           -X github.com/kro-run/kro/pkg.GitCommit=$(GIT_COMMIT) \
+           -X github.com/kro-run/kro/pkg.BuildDate=$(BUILD_DATE)
+
 WITH_GOFLAGS = GOFLAGS="$(GOFLAGS)"
 
 HELM_DIR = ./helm
@@ -106,7 +111,7 @@ lint-fix: golangci-lint ## Run golangci-lint linter and perform fixes
 
 .PHONY: build
 build: manifests generate fmt vet ## Build controller binary.
-	go build -o bin/controller ./cmd/controller/main.go
+	go build -ldflags=${LDFLAGS} -o bin/controller ./cmd/controller/main.go
 
 .PHONY: run
 run: manifests generate fmt vet ## Run a controller from your host.
@@ -188,13 +193,15 @@ $(CONTROLLER_GEN): $(LOCALBIN)
 build-image: ko ## Build the kro controller images using ko build
 	echo "Building kro image $(RELEASE_VERSION).."
 	$(WITH_GOFLAGS) KOCACHE=$(KOCACHE) KO_DOCKER_REPO=$(KO_DOCKER_REPO) \
+		GIT_COMMIT=$(GIT_COMMIT) RELEASE_VERSION=$(RELEASE_VERSION) BUILD_DATE=$(BUILD_DATE) \
 		$(KO) build --bare github.com/kro-run/kro/cmd/controller \
 		--local\
 		--push=false --tags ${RELEASE_VERSION} --sbom=none
 
 .PHONY: publish
 publish-image: ko ## Publish the kro controller images to ghcr.io
-	$(WITH_GOFLAGS) KOCACHE=$(KOCACHE) \
+	$(WITH_GOFLAGS) KOCACHE=$(KOCACHE) KO_DOCKER_REPO=$(KO_DOCKER_REPO) \
+		GIT_COMMIT=$(GIT_COMMIT) RELEASE_VERSION=$(RELEASE_VERSION) BUILD_DATE=$(BUILD_DATE) \
 		$(KO) publish --bare github.com/kro-run/kro/cmd/controller \
 		--tags ${RELEASE_VERSION} --sbom=none
 
@@ -229,7 +236,7 @@ uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified
 
 .PHONY: deploy-kind
 deploy-kind: export KO_DOCKER_REPO=kind.local
-deploy-kind:
+deploy-kind: ko
 	$(KIND) delete clusters ${KIND_CLUSTER_NAME} || true
 	$(KIND) create cluster --name ${KIND_CLUSTER_NAME}
 	$(KUBECTL) --context kind-$(KIND_CLUSTER_NAME) create namespace kro-system
diff --git a/README.md b/README.md
index 952435d..903031d 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,13 @@
 # kro | Kube Resource Orchestrator
 
+
+[![Go Report Card](https://goreportcard.com/badge/github.com/kro-run/kro)](https://goreportcard.com/report/github.com/kro-run/kro)
+[![unit tests](https://github.com/kro-run/kro/actions/workflows/unit-tests.yaml/badge.svg)](https://github.com/kro-run/kro/actions/workflows/unit-tests.yaml)
+![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/kro-run/Kro)
+![GitHub License](https://img.shields.io/github/license/kro-run/kro)
+[![Build and Publish](https://github.com/kro-run/kro/actions/workflows/build-push-image.yaml/badge.svg?branch=main)](https://github.com/kro-run/kro/actions/workflows/build-push-image.yaml)
+![GitHub Repo stars](https://img.shields.io/github/stars/kro-run/kro)
+
 This project aims to simplify the creation and management of complex custom resources for Kubernetes.
 
 Kube Resource Orchestrator (**kro**) helps you to define complex multi-resource constructs as reusable components in your applications and systems. It does this by providing a Kubernetes-native, vendor agnostic way to define groupings of Kubernetes resources. 
diff --git a/api/v1alpha1/groupversion_info.go b/api/v1alpha1/groupversion_info.go
index b6aeb99..f21a308 100644
--- a/api/v1alpha1/groupversion_info.go
+++ b/api/v1alpha1/groupversion_info.go
@@ -22,12 +22,12 @@ import (
 )
 
 const (
-	KroDomainName = "kro.run"
+	KRODomainName = "kro.run"
 )
 
 var (
 	// GroupVersion is group version used to register these objects
-	GroupVersion = schema.GroupVersion{Group: KroDomainName, Version: "v1alpha1"}
+	GroupVersion = schema.GroupVersion{Group: KRODomainName, Version: "v1alpha1"}
 
 	// SchemeBuilder is used to add go types to the GroupVersionKind scheme
 	SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
diff --git a/api/v1alpha1/types.go b/api/v1alpha1/types.go
index 77aa01f..54ae3a2 100644
--- a/api/v1alpha1/types.go
+++ b/api/v1alpha1/types.go
@@ -52,12 +52,14 @@ type Schema struct {
 	// and create the CRD for the resourcegraphdefinition.
 	//
 	// +kubebuilder:validation:Required
+	// +kubebuilder:validation:Pattern=`^[A-Z][a-zA-Z0-9]{0,62}$`
 	// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="kind is immutable"
 	Kind string `json:"kind,omitempty"`
 	// The APIVersion of the resourcegraphdefinition. This is used to generate
 	// and create the CRD for the resourcegraphdefinition.
 	//
 	// +kubebuilder:validation:Required
+	// +kubebuilder:validation:Pattern=`^v[0-9]+(alpha[0-9]+|beta[0-9]+)?$`
 	// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="apiVersion is immutable"
 	APIVersion string `json:"apiVersion,omitempty"`
 	// The group of the resourcegraphdefinition. This is used to set the API group
diff --git a/cmd/controller/main.go b/cmd/controller/main.go
index a99c12e..6f9e24c 100644
--- a/cmd/controller/main.go
+++ b/cmd/controller/main.go
@@ -91,7 +91,7 @@ func main() {
 		"dynamic-controller-concurrent-reconciles", 1,
 		"The number of dynamic controller reconciles to run in parallel",
 	)
-	// reconciler parametes
+	// reconciler parameters
 	flag.IntVar(&resyncPeriod, "dynamic-controller-default-resync-period", 10,
 		"interval at which the controller will re list resources even with no changes, in hours")
 	flag.IntVar(&queueMaxRetries, "dynamic-controller-default-queue-max-retries", 20,
diff --git a/cmd/kro/main.go b/cmd/kro/main.go
new file mode 100644
index 0000000..3f60aea
--- /dev/null
+++ b/cmd/kro/main.go
@@ -0,0 +1,34 @@
+// Copyright 2025 The Kube Resource Orchestrator Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"). You may
+// not use this file except in compliance with the License. A copy of the
+// License is located at
+//
+//	http://www.apache.org/licenses/LICENSE-2.0
+//
+// or in the "license" file accompanying this file. This file is distributed
+// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+// express or implied. See the License for the specific language governing
+// permissions and limitations under the License.
+package main
+
+import (
+	"os"
+
+	"github.com/spf13/cobra"
+)
+
+var rootCmd = &cobra.Command{
+	Use:   "kro",
+	Short: "kro is a CLI for https://kro.run",
+}
+
+func main() {
+	if err := rootCmd.Execute(); err != nil {
+		os.Exit(1)
+	}
+}
+
+func init() {
+	// Add subcommands and configure global flags here
+}
diff --git a/config/crd/bases/kro.run_resourcegraphdefinitions.yaml b/config/crd/bases/kro.run_resourcegraphdefinitions.yaml
index b26d4d6..c65362c 100644
--- a/config/crd/bases/kro.run_resourcegraphdefinitions.yaml
+++ b/config/crd/bases/kro.run_resourcegraphdefinitions.yaml
@@ -101,6 +101,7 @@ spec:
                     description: |-
                       The APIVersion of the resourcegraphdefinition. This is used to generate
                       and create the CRD for the resourcegraphdefinition.
+                    pattern: ^v[0-9]+(alpha[0-9]+|beta[0-9]+)?$
                     type: string
                     x-kubernetes-validations:
                     - message: apiVersion is immutable
@@ -115,6 +116,7 @@ spec:
                     description: |-
                       The kind of the resourcegraphdefinition. This is used to generate
                       and create the CRD for the resourcegraphdefinition.
+                    pattern: ^[A-Z][a-zA-Z0-9]{0,62}$
                     type: string
                     x-kubernetes-validations:
                     - message: kind is immutable
@@ -167,7 +169,7 @@ spec:
                       format: date-time
                       type: string
                     message:
-                      description: A human readable message indicating details about
+                      description: A human-readable message indicating details about
                         the transition.
                       type: string
                     observedGeneration:
diff --git a/docs/developer-getting-started.md b/docs/developer-getting-started.md
index 23acd5e..45bbaf9 100644
--- a/docs/developer-getting-started.md
+++ b/docs/developer-getting-started.md
@@ -23,7 +23,7 @@ To test and run the project with your local changes, follow these steps to set u
 
 3. Install the Custom Resource Definitions (CRDs): Apply the latest CRDs to your cluster:
     ```bash
-    make generate
+    make manifests
     kubectl apply -k ./config/crd
     ```
 
diff --git a/examples/apigateway/vpc-lattice/README.md b/examples/apigateway/vpc-lattice/README.md
new file mode 100644
index 0000000..32883e4
--- /dev/null
+++ b/examples/apigateway/vpc-lattice/README.md
@@ -0,0 +1,170 @@
+# KRO VPC Lattice Demo
+
+This example demonstrates how to use KRO (Kubernetes Resource Orchestrator) to deploy and manage Amazon VPC Lattice resources on an Amazon EKS cluster, providing a streamlined approach to service networking in Kubernetes.
+
+## Overview
+
+Amazon VPC Lattice is a fully managed application networking service that enables you to connect, secure, and monitor all your services across multiple accounts and virtual private clouds (VPCs). This demo shows how to:
+
+1. Use KRO to define reusable resource graphs for VPC Lattice integration
+2. Implement Kubernetes Gateway API with the AWS Gateway Controller
+3. Deploy applications with simplified networking configuration
+4. Connect Kubernetes services to the VPC Lattice service network
+
+![Architecture Diagram](images/draw.png)
+
+
+## Prerequisites
+
+- Amazon EKS cluster
+- AWS CLI configured with appropriate permissions
+- kubectl installed and configured
+- Helm v3
+- jq
+
+## Installation
+
+### 1. Install KRO
+
+KRO (Kubernetes Resource Orchestrator) provides a way to define, deploy and manage complex Kubernetes resources as reusable templates.
+
+```bash
+export KRO_VERSION=$(curl -sL \
+    https://api.github.com/repos/kro-run/kro/releases/latest | \
+    jq -r '.tag_name | ltrimstr("v")'
+  )
+helm install kro oci://ghcr.io/kro-run/kro/kro \
+  --namespace kro \
+  --create-namespace \
+  --version=${KRO_VERSION}
+```
+
+### 2. Install Kubernetes Gateway API and AWS Gateway API Controller
+
+This step installs the Kubernetes Gateway API CRDs and the AWS Gateway API Controller for Amazon VPC Lattice integration.
+
+First, configure the necessary security group rules to allow VPC Lattice to communicate with your EKS cluster:
+
+```bash
+# Set environment variables
+export AWS_REGION=<your-aws-region>
+export EKS_CLUSTER_NAME=<your-eks-cluster-name>
+export LATTICE_IAM_ROLE=<your-lattice-iam-role-arn>
+
+# Configure security group rules
+CLUSTER_SG=$(aws eks describe-cluster --name $EKS_CLUSTER_NAME --output json| jq -r '.cluster.resourcesVpcConfig.clusterSecurityGroupId')
+PREFIX_LIST_ID=$(aws ec2 describe-managed-prefix-lists --query "PrefixLists[?PrefixListName=='com.amazonaws.$AWS_REGION.vpc-lattice'].PrefixListId" | jq -r '.[]')
+aws ec2 authorize-security-group-ingress --group-id $CLUSTER_SG --ip-permissions "PrefixListIds=[{PrefixListId=${PREFIX_LIST_ID}}],IpProtocol=-1"
+PREFIX_LIST_ID_IPV6=$(aws ec2 describe-managed-prefix-lists --query "PrefixLists[?PrefixListName=='com.amazonaws.$AWS_REGION.ipv6.vpc-lattice'].PrefixListId" | jq -r '.[]')
+aws ec2 authorize-security-group-ingress --group-id $CLUSTER_SG --ip-permissions "PrefixListIds=[{PrefixListId=${PREFIX_LIST_ID_IPV6}}],IpProtocol=-1"
+```
+
+Next, install the Kubernetes Gateway API and AWS Gateway Controller:
+
+```bash
+# Install Kubernetes Gateway API CRDs
+kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.2.0/standard-install.yaml
+
+# Log in to AWS ECR public registry
+aws ecr-public get-login-password --region us-east-1 \
+  | helm registry login --username AWS --password-stdin public.ecr.aws
+
+# Get latest controller version
+LATTICE_CONTROLLER_VERSION=$(curl -sL \
+    https://api.github.com/repos/aws/aws-application-networking-k8s/releases/latest | \
+    jq -r '.tag_name | ltrimstr("v")'
+  )
+
+# Install AWS Gateway Controller
+helm install gateway-api-controller \
+    oci://public.ecr.aws/aws-application-networking-k8s/aws-gateway-controller-chart \
+    --version=v${LATTICE_CONTROLLER_VERSION} \
+    --create-namespace \
+    --set=aws.region=${AWS_REGION} \
+    --set serviceAccount.annotations."eks\.amazonaws\.com/role-arn"="$LATTICE_IAM_ROLE" \
+    --set=defaultServiceNetwork=kro-vpc-lattice-demo \
+    --namespace gateway-api-controller \
+    --wait
+```
+
+### Deploy gatewayClass and gateway resources 
+```
+kubectl apply -f resources/gatewayclass.yaml
+kubectl apply -f resources/gateway.yaml
+```
+
+
+## Resource Graph Definitions
+
+### 3. Deploy the Lattice Resource Graph Definition
+
+Create a Lattice Resource Graph Definition that maps VPC Lattice components to Kubernetes resources:
+
+```bash
+kubectl apply -f rg-lattice.yaml
+```
+
+The `rg-lattice.yaml` defines how to translate a simple schema into VPC Lattice-related Kubernetes Gateway API resources, including TargetGroupPolicy, and HTTPRoute.
+
+### 4. Deploy the WebApp Resource Graph Definition
+
+Create a WebApp Resource Graph Definition to simplify application deployment:
+
+```bash
+kubectl apply -f rg-webapp.yaml
+```
+
+The `rg-webapp.yaml` defines a template for deploying a web application with a Kubernetes Deployment and Service.
+
+### 5. Deploy the WebService Resource Graph Definition
+
+Create a WebService Resource Graph Definition that combines the WebApp and Lattice resources:
+
+```bash
+kubectl apply -f rg-webservice.yaml
+```
+
+The `rg-webservice.yaml` combines the WebApp and Lattice resource graphs, allowing you to deploy a web service with optional VPC Lattice integration.
+
+## Deploying Your Application
+
+### 6. Deploy Your Application Instance
+
+Create an instance of the WebService with VPC Lattice integration enabled:
+
+```bash
+kubectl apply -f instance.yaml
+```
+
+## Architecture
+
+This project implements a three-layer architecture:
+
+1. **Base Resources Layer**: Core Kubernetes resources (TargetGroupPolicy, HTTPRoute, Deployment, Service)
+2. **Resource Graph Layer**: Reusable templates (Lattice, WebApp, WebService) that abstract the base resources
+3. **Instance Layer**: Application-specific instances created from the resource graphs
+
+## Accessing Your Service
+
+Once deployed, your service will be accessible through the VPC Lattice service network. You can find the VPC Lattice endpoint using the AWS Console or the following AWS CLI command:
+
+```bash
+aws vpc-lattice list-service-networks
+aws vpc-lattice list-services --service-network-identifier kro-vpc-lattice-demo
+```
+
+## Benefits of This Approach
+
+- **Simplified Configuration**: Reduce complexity by abstracting away the detailed configuration of VPC Lattice resources
+- **Reusable Templates**: Create standardized deployment patterns that can be reused across multiple applications
+- **Declarative Approach**: Define the desired state of your infrastructure and application components
+- **Improved Developer Experience**: Allow developers to deploy applications with VPC Lattice integration without deep networking knowledge
+
+## Troubleshooting
+
+- Ensure your EKS cluster has the required IAM permissions for VPC Lattice integration
+- Verify the security group rules are correctly configured to allow VPC Lattice traffic
+- Check the Gateway API Controller logs for any errors:
+  ```bash
+  kubectl logs -n gateway-api-controller -l app.kubernetes.io/name=gateway-api-controller
+  ```
diff --git a/examples/apigateway/vpc-lattice/images/draw.png b/examples/apigateway/vpc-lattice/images/draw.png
new file mode 100644
index 0000000..de908e4
Binary files /dev/null and b/examples/apigateway/vpc-lattice/images/draw.png differ
diff --git a/examples/apigateway/vpc-lattice/instance.yaml b/examples/apigateway/vpc-lattice/instance.yaml
new file mode 100644
index 0000000..e0d3dfe
--- /dev/null
+++ b/examples/apigateway/vpc-lattice/instance.yaml
@@ -0,0 +1,10 @@
+apiVersion: kro.run/v1alpha1
+kind: WebService
+metadata:
+  name: test-app
+spec:
+  name: test-app
+  image: nginx
+  service: {}
+  vpclattice:
+    enabled: true
diff --git a/examples/apigateway/vpc-lattice/resources/gateway.yaml b/examples/apigateway/vpc-lattice/resources/gateway.yaml
new file mode 100644
index 0000000..e8f85b8
--- /dev/null
+++ b/examples/apigateway/vpc-lattice/resources/gateway.yaml
@@ -0,0 +1,14 @@
+apiVersion: gateway.networking.k8s.io/v1
+kind: Gateway
+metadata:
+  name: kro-vpc-lattice-demo
+  namespace: default
+spec:
+  gatewayClassName: amazon-vpc-lattice
+  listeners:
+  - allowedRoutes:
+      namespaces:
+        from: Same
+    name: http
+    port: 80
+    protocol: HTTP
\ No newline at end of file
diff --git a/examples/apigateway/vpc-lattice/resources/gatewayclass.yaml b/examples/apigateway/vpc-lattice/resources/gatewayclass.yaml
new file mode 100644
index 0000000..472474e
--- /dev/null
+++ b/examples/apigateway/vpc-lattice/resources/gatewayclass.yaml
@@ -0,0 +1,6 @@
+apiVersion: gateway.networking.k8s.io/v1
+kind: GatewayClass
+metadata:
+  name: amazon-vpc-lattice
+spec:
+  controllerName: application-networking.k8s.aws/gateway-api-controller
diff --git a/examples/apigateway/vpc-lattice/rg-lattice.yaml b/examples/apigateway/vpc-lattice/rg-lattice.yaml
new file mode 100644
index 0000000..26cb63e
--- /dev/null
+++ b/examples/apigateway/vpc-lattice/rg-lattice.yaml
@@ -0,0 +1,60 @@
+apiVersion: kro.run/v1alpha1
+kind: ResourceGraphDefinition
+metadata:
+  name: lattice.kro.run
+spec:
+  schema:
+    apiVersion: v1alpha1
+    kind: Lattice
+    spec:
+      name: string
+      namespace: string | default=default
+      port: integer | default=80
+      vpcLatticeName: string | default="kro-vpc-lattice-demo"
+      listenerProtocol: string | default="HTTP"
+
+  resources:
+  - id: targetgrouppolicy
+    template:
+      apiVersion: application-networking.k8s.aws/v1alpha1
+      kind: TargetGroupPolicy
+      metadata:
+        name: ${schema.spec.vpcLatticeName}
+        namespace: ${schema.spec.namespace}
+      spec:
+        targetRef:
+          group: ""
+          kind: Service
+          name: ${schema.spec.name}
+        protocol: ${schema.spec.listenerProtocol}
+        protocolVersion: HTTP1
+        healthCheck:
+          enabled: true
+          intervalSeconds: 10
+          timeoutSeconds: 1
+          healthyThresholdCount: 3
+          unhealthyThresholdCount: 2
+          path: "/"
+          port: ${schema.spec.port}
+          protocol: HTTP
+          protocolVersion: HTTP1
+          statusMatch: "200"
+
+  - id: httproute
+    template:
+      apiVersion: gateway.networking.k8s.io/v1beta1
+      kind: HTTPRoute
+      metadata:
+        name: ${schema.spec.name}
+        namespace: ${schema.spec.namespace}
+      spec:
+        parentRefs:
+          - name: ${schema.spec.vpcLatticeName}
+            sectionName: http
+        rules:
+          - backendRefs:
+              - name: ${schema.spec.name}
+                namespace: ${schema.spec.namespace}
+                kind: Service
+                port: ${schema.spec.port}
+                weight: 100
diff --git a/examples/apigateway/vpc-lattice/rg-webapp.yaml b/examples/apigateway/vpc-lattice/rg-webapp.yaml
new file mode 100644
index 0000000..b741f0a
--- /dev/null
+++ b/examples/apigateway/vpc-lattice/rg-webapp.yaml
@@ -0,0 +1,78 @@
+apiVersion: kro.run/v1alpha1
+kind: ResourceGraphDefinition
+metadata:
+  name: webapp.kro.run
+spec:
+  schema:
+    apiVersion: v1alpha1
+    kind: WebApp
+    spec:
+      name: string
+      namespace: string | default=default
+      image: string | default=nginx
+      port: integer | default=80
+      replicas: integer | default=3
+      service:
+        enabled: boolean | default=true
+      serviceAccount: string | default=default
+    status:
+      deploymentConditions: ${deployment.status.conditions}
+      availableReplicas: ${deployment.status.availableReplicas}
+
+  resources:
+  - id: deployment
+    readyWhen:
+      - ${deployment.spec.replicas == deployment.status.availableReplicas}
+    template:
+      apiVersion: apps/v1
+      kind: Deployment
+      metadata:
+        name: ${schema.spec.name}
+        namespace: ${schema.spec.namespace}
+        labels:
+          app.kubernetes.io/name: ${schema.spec.name}
+      spec:
+        replicas: ${schema.spec.replicas}
+        selector:
+          matchLabels:
+            app.kubernetes.io/name: ${schema.spec.name}
+            app: ${schema.spec.name}
+        template:
+          metadata:
+            labels:
+              app.kubernetes.io/name: ${schema.spec.name}
+              app: ${schema.spec.name}
+          spec:
+            serviceAccountName: ${schema.spec.serviceAccount}
+            containers:
+            - name: ${schema.spec.name}
+              image: ${schema.spec.image}
+              imagePullPolicy: Always
+              ports:
+              - containerPort: ${schema.spec.port}
+              resources:
+                requests:
+                  memory: "64Mi"
+                  cpu: "250m"
+                limits:
+                  memory: "1Gi"
+                  cpu: "1"
+            restartPolicy: Always
+
+  - id: service
+    includeWhen:
+    - ${schema.spec.service.enabled}  
+    template:
+      apiVersion: v1
+      kind: Service
+      metadata:
+        name: ${deployment.metadata.name}
+        namespace: ${deployment.metadata.namespace}
+      spec:
+        selector:
+          app: ${schema.spec.name}
+        ports:
+        - name: http
+          protocol: TCP
+          port: ${schema.spec.port}
+          targetPort: ${schema.spec.port}
diff --git a/examples/apigateway/vpc-lattice/rg-webservice.yaml b/examples/apigateway/vpc-lattice/rg-webservice.yaml
new file mode 100644
index 0000000..ccf1355
--- /dev/null
+++ b/examples/apigateway/vpc-lattice/rg-webservice.yaml
@@ -0,0 +1,59 @@
+apiVersion: kro.run/v1alpha1
+kind: ResourceGraphDefinition
+metadata:
+  name: webservice.kro.run
+spec:
+  schema:
+    apiVersion: v1alpha1
+    kind: WebService
+    spec:
+      name: string
+      namespace: string | default=default
+      image: string | default=nginx
+      port: integer | default=80
+      replicas: integer | default=3
+      service:
+        enabled: boolean | default=true
+      serviceAccount: string | default=default
+      vpclattice:
+        enabled: boolean | default=false
+      vpcLatticeName: string | default="kro-vpc-lattice-demo"
+      gatewayClassName: string | default=amazon-vpc-lattice
+      listenerProtocol: string | default="HTTP"
+
+
+  resources:
+  - id: webapp
+    template:
+      apiVersion: kro.run/v1alpha1
+      kind: WebApp
+      metadata:
+        name: ${schema.spec.name}
+        namespace: ${schema.spec.namespace}
+        labels:
+          app.kubernetes.io/name: ${schema.spec.name}
+      spec:
+        name: ${schema.spec.name}
+        namespace: ${schema.spec.namespace}
+        image: ${schema.spec.image}
+        port: ${schema.spec.port}
+        replicas: ${schema.spec.replicas}
+        service:
+          enabled: ${schema.spec.service.enabled}
+        serviceAccount: ${schema.spec.serviceAccount}
+
+  - id: lattice
+    includeWhen:
+    - ${schema.spec.vpclattice.enabled}
+    template:
+      apiVersion: kro.run/v1alpha1
+      kind: Lattice
+      metadata:
+        name: ${schema.spec.name}
+      spec:
+        name: ${schema.spec.name}
+        namespace: ${schema.spec.namespace}
+        port: ${schema.spec.port}
+        vpcLatticeName: ${schema.spec.vpcLatticeName}
+        listenerProtocol: ${schema.spec.listenerProtocol}
+        gatewayClassName: ${schema.spec.gatewayClassName}
diff --git a/examples/aws/llm/llm.yaml b/examples/aws/llm/llm.yaml
index 31eff03..0b5a2d4 100644
--- a/examples/aws/llm/llm.yaml
+++ b/examples/aws/llm/llm.yaml
@@ -45,7 +45,6 @@ spec:
       kind: StorageClass
       metadata:
         name: ${schema.spec.name}-storage-class
-        namespace: ${schema.spec.namespace}
         annotations:
           storageclass.kubernetes.io/is-default-class: "true"
       parameters:
diff --git a/examples/kubernetes/cel-functions/rg.yaml b/examples/kubernetes/cel-functions/rg.yaml
index 7a188ad..44cddaf 100644
--- a/examples/kubernetes/cel-functions/rg.yaml
+++ b/examples/kubernetes/cel-functions/rg.yaml
@@ -13,49 +13,49 @@ spec:
       deploymentConditions: ${deployment.status.conditions}
       availableReplicas: ${deployment.status.availableReplicas * 10}
   resources:
-  - id: deployment
-    template:
-      apiVersion: apps/v1
-      kind: Deployment
-      metadata:
-        name: ${schema.spec.name}
-      spec:
-        replicas: 1
-        selector:
-          matchLabels:
-            app: deployment
-        template:
-          metadata:
-            labels:
+    - id: deployment
+      template:
+        apiVersion: apps/v1
+        kind: Deployment
+        metadata:
+          name: ${schema.spec.name}
+        spec:
+          replicas: 1
+          selector:
+            matchLabels:
               app: deployment
-          spec:
-            containers:
-            - name: ${schema.spec.name}-deployment
-              image: nginx
-              ports:
-              - containerPort: 80
-              env:
-              - name: FULL_DNS_NAME
-                value: ${schema.spec.fullDNSName}
-              - name: DNS_FIRST_PART
-                value: ${schema.spec.fullDNSName.split('.')[0]}
-              - name: DNS_PARTIALS
-                value: ${schema.spec.fullDNSName.split('.').slice(0, 1).join('.')}
-  - id: service
-    template:
-      apiVersion: v1
-      kind: Service
-      metadata:
-        name: ${schema.spec.name}
-        labels:
-          app: deployment
-          dns: ${schema.spec.fullDNSName}
-          partials: "${size(schema.spec.fullDNSName.split('.')) > 2 ? schema.spec.fullDNSName.split('.').slice(0, 3).join('.') : schema.spec.fullDNSName}"
-          first_part: ${schema.schema.spec.fullDNSName.split('.')[0]}
-      spec:
-        selector:
-          app: deployment
-        ports:
-        - protocol: TCP
-          port: 80
-          targetPort: 80
\ No newline at end of file
+          template:
+            metadata:
+              labels:
+                app: deployment
+            spec:
+              containers:
+                - name: ${schema.spec.name}-deployment
+                  image: nginx
+                  ports:
+                    - containerPort: 80
+                  env:
+                    - name: FULL_DNS_NAME
+                      value: ${schema.spec.fullDNSName}
+                    - name: DNS_FIRST_PART
+                      value: ${schema.spec.fullDNSName.split('.')[0]}
+                    - name: DNS_PARTIALS
+                      value: ${schema.spec.fullDNSName.split('.').slice(0, 1).join('.')}
+    - id: service
+      template:
+        apiVersion: v1
+        kind: Service
+        metadata:
+          name: ${schema.spec.name}
+          labels:
+            app: deployment
+            dns: ${schema.spec.fullDNSName}
+            partials: "${size(schema.spec.fullDNSName.split('.')) > 2 ? schema.spec.fullDNSName.split('.').slice(0, 3).join('.') : schema.spec.fullDNSName}"
+            first_part: ${schema.spec.fullDNSName.split('.')[0]}
+        spec:
+          selector:
+            app: deployment
+          ports:
+            - protocol: TCP
+              port: 80
+              targetPort: 80
diff --git a/go.mod b/go.mod
index a6aba9e..9aade2a 100644
--- a/go.mod
+++ b/go.mod
@@ -5,10 +5,11 @@ go 1.24.0
 require (
 	github.com/go-logr/logr v1.4.2
 	github.com/gobuffalo/flect v1.0.2
-	github.com/google/cel-go v0.22.0
+	github.com/google/cel-go v0.24.1
 	github.com/onsi/ginkgo/v2 v2.20.0
 	github.com/onsi/gomega v1.34.1
 	github.com/prometheus/client_golang v1.19.1
+	github.com/spf13/cobra v1.8.1
 	github.com/stretchr/testify v1.9.0
 	go.uber.org/zap v1.26.0
 	golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
@@ -24,7 +25,7 @@ require (
 )
 
 require (
-	cel.dev/expr v0.18.0 // indirect
+	cel.dev/expr v0.19.1 // indirect
 	github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
 	github.com/cespare/xxhash/v2 v2.3.0 // indirect
@@ -48,6 +49,7 @@ require (
 	github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect
 	github.com/google/uuid v1.6.0 // indirect
 	github.com/imdario/mergo v0.3.6 // indirect
+	github.com/inconshreveable/mousetrap v1.1.0 // indirect
 	github.com/josharian/intern v1.0.0 // indirect
 	github.com/json-iterator/go v1.1.12 // indirect
 	github.com/mailru/easyjson v0.7.7 // indirect
@@ -63,11 +65,11 @@ require (
 	github.com/stoewer/go-strcase v1.2.0 // indirect
 	github.com/x448/float16 v0.8.4 // indirect
 	go.uber.org/multierr v1.11.0 // indirect
-	golang.org/x/net v0.33.0 // indirect
+	golang.org/x/net v0.36.0 // indirect
 	golang.org/x/oauth2 v0.21.0 // indirect
-	golang.org/x/sys v0.28.0 // indirect
-	golang.org/x/term v0.27.0 // indirect
-	golang.org/x/text v0.21.0 // indirect
+	golang.org/x/sys v0.30.0 // indirect
+	golang.org/x/term v0.29.0 // indirect
+	golang.org/x/text v0.22.0 // indirect
 	golang.org/x/tools v0.24.0 // indirect
 	gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
 	google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7 // indirect
diff --git a/go.sum b/go.sum
index 004abe6..7235610 100644
--- a/go.sum
+++ b/go.sum
@@ -1,11 +1,12 @@
-cel.dev/expr v0.18.0 h1:CJ6drgk+Hf96lkLikr4rFf19WrU0BOWEihyZnI2TAzo=
-cel.dev/expr v0.18.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw=
+cel.dev/expr v0.19.1 h1:NciYrtDRIR0lNCnH1LFJegdjspNx9fI59O7TWcua/W4=
+cel.dev/expr v0.19.1/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw=
 github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
 github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
 github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
 github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
@@ -40,8 +41,8 @@ github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l
 github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
 github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
-github.com/google/cel-go v0.22.0 h1:b3FJZxpiv1vTMo2/5RDUqAHPxkT8mmMfJIrq1llbf7g=
-github.com/google/cel-go v0.22.0/go.mod h1:BuznPXXfQDpXKWQ9sPW3TzlAJN5zzFe+i9tIs0yC4s8=
+github.com/google/cel-go v0.24.1 h1:jsBCtxG8mM5wiUJDSGUqU0K7Mtr3w7Eyv00rw4DiZxI=
+github.com/google/cel-go v0.24.1/go.mod h1:Hdf9TqOaTNSFQA1ybQaRqATVoK7m/zcf7IMhGXP5zI8=
 github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
 github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
@@ -56,6 +57,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
 github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28=
 github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
+github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
+github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
 github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
 github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
 github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
@@ -94,6 +97,9 @@ github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0leargg
 github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
 github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
 github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
+github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
+github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
 github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU=
@@ -129,8 +135,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
-golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
+golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA=
+golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I=
 golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
 golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -139,14 +145,14 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
-golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
-golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
+golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
+golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
+golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
-golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
+golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
+golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
 golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
 golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
diff --git a/helm/Chart.yaml b/helm/Chart.yaml
index 320df3f..0de7bc4 100644
--- a/helm/Chart.yaml
+++ b/helm/Chart.yaml
@@ -2,6 +2,6 @@ apiVersion: v2
 name: kro
 description: A Helm chart for kro
 type: application
-version: 0.2.1
-appVersion: "0.2.1"
+version: 0.2.2
+appVersion: "0.2.2"
 home: https://kro.run/
diff --git a/helm/crds/kro.run_resourcegraphdefinitions.yaml b/helm/crds/kro.run_resourcegraphdefinitions.yaml
index b26d4d6..c65362c 100644
--- a/helm/crds/kro.run_resourcegraphdefinitions.yaml
+++ b/helm/crds/kro.run_resourcegraphdefinitions.yaml
@@ -101,6 +101,7 @@ spec:
                     description: |-
                       The APIVersion of the resourcegraphdefinition. This is used to generate
                       and create the CRD for the resourcegraphdefinition.
+                    pattern: ^v[0-9]+(alpha[0-9]+|beta[0-9]+)?$
                     type: string
                     x-kubernetes-validations:
                     - message: apiVersion is immutable
@@ -115,6 +116,7 @@ spec:
                     description: |-
                       The kind of the resourcegraphdefinition. This is used to generate
                       and create the CRD for the resourcegraphdefinition.
+                    pattern: ^[A-Z][a-zA-Z0-9]{0,62}$
                     type: string
                     x-kubernetes-validations:
                     - message: kind is immutable
@@ -167,7 +169,7 @@ spec:
                       format: date-time
                       type: string
                     message:
-                      description: A human readable message indicating details about
+                      description: A human-readable message indicating details about
                         the transition.
                       type: string
                     observedGeneration:
diff --git a/helm/templates/cluster-role-binding.yaml b/helm/templates/cluster-role-binding.yaml
index 5f21eb1..395917e 100644
--- a/helm/templates/cluster-role-binding.yaml
+++ b/helm/templates/cluster-role-binding.yaml
@@ -1,13 +1,13 @@
 apiVersion: rbac.authorization.k8s.io/v1
 kind: ClusterRoleBinding
 metadata:
-  name: {{ include "kro.fullname" . }}-role-binding
+  name: {{ include "kro.fullname" . }}{{- if eq .Values.rbac.mode "unrestricted" -}}-cluster-role-binding{{- else -}}:controller{{- end }}
   labels:
     {{- include "kro.labels" . | nindent 4 }}
 roleRef:
   kind: ClusterRole
   apiGroup: rbac.authorization.k8s.io
-  name: {{ include "kro.fullname" . }}-cluster-role
+  name: {{ include "kro.fullname" . }}{{- if eq .Values.rbac.mode "unrestricted" -}}-cluster-role{{- else -}}:controller{{- end }}
 subjects:
 - kind: ServiceAccount
   name: {{ include "kro.serviceAccountName" . }}
diff --git a/helm/templates/cluster-role.yaml b/helm/templates/cluster-role.yaml
index 9237a45..cae1b3c 100644
--- a/helm/templates/cluster-role.yaml
+++ b/helm/templates/cluster-role.yaml
@@ -1,9 +1,24 @@
 apiVersion: rbac.authorization.k8s.io/v1
 kind: ClusterRole
 metadata:
-  name: {{ include "kro.fullname" . }}-cluster-role
+  annotations:
+    kubernetes.io/description: |
+{{- if .Values.rbac.legacy }}
+      This ClusterRole grants cluster-wide admin access for the kro controller.
+{{- else }}
+      This ClusterRole grants access for the kro controller.
+
+      The kro installation comes with enough for the controller to run, and start watching the relevant resources
+      (e.g. ResourceGraphDefinitions), but in order to allow kro to create and manage other types of resoruces you
+      must manually grant access to those separately.
+
+      The easiest way to accomplish this is to create a new ClusterRole with the label `rbac.kro.run/aggregate-to-controller: "true"`
+      which will be automatically aggregated into this one.
+{{- end }}
+  name: {{ include "kro.fullname" . }}{{- if eq .Values.rbac.mode "unrestricted" -}}-cluster-role{{- else -}}:controller{{- end }}
   labels:
     {{- include "kro.labels" . | nindent 4 }}
+{{ if eq .Values.rbac.mode "unrestricted" -}}
 rules:
 - apiGroups:
   - "*"
@@ -11,3 +26,58 @@ rules:
   - "*"
   verbs:
   - '*'
+{{ else if eq .Values.rbac.mode "aggregation" -}}
+aggregationRule:
+  clusterRoleSelectors:
+    - matchLabels:
+        'rbac.kro.run/aggregate-to-controller': "true"
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  annotations:
+    kubernetes.io/description: |
+      This ClusterRole grants access for the kro controller to resources it always needs access to.
+  labels:
+    {{- include "kro.labels" . | nindent 4 }}
+    rbac.kro.run/aggregate-to-controller: "true"
+  name: {{ include "kro.fullname" . }}:controller:static
+rules:
+- apiGroups:
+  - kro.run
+  resources:
+  - resourcegraphdefinitions
+  verbs:
+  - create
+  - delete
+  - get
+  - list
+  - patch
+  - update
+  - watch
+- apiGroups:
+  - kro.run
+  resources:
+  - resourcegraphdefinitions/finalizers
+  verbs:
+  - update
+- apiGroups:
+  - kro.run
+  resources:
+  - resourcegraphdefinitions/status
+  verbs:
+  - get
+  - patch
+  - update
+- apiGroups:
+  - apiextensions.k8s.io
+  resources:
+  - customresourcedefinitions
+  verbs:
+  - get
+  - list
+  - watch
+  - patch
+  - update
+  - delete
+{{- end }}
diff --git a/helm/values.yaml b/helm/values.yaml
index dca9f22..d660e3e 100644
--- a/helm/values.yaml
+++ b/helm/values.yaml
@@ -11,7 +11,7 @@ image:
   # The location of the container image repository
   repository: ghcr.io/kro-run/kro/controller
   # Overrides the image tag whose default is the chart appVersion.
-  tag: "0.2.1"
+  tag: "0.2.2"
   # Ignores the repository and tag settings and uses the controllers ko entrypoint.
   ko: false
   # Image pull policy (IfNotPresent: pull the image only if it is not present locally)
@@ -31,6 +31,18 @@ serviceAccount:
   # If not set and create is true, a name is generated using the fullname template
   name: ""
 
+rbac:
+  # Specifies how to manage access for kro in the cluster
+  # Two modes are currently supported:
+  # - unrestricted: Grants cluster-wide full access to all resources
+  # - aggregation: Grants a minimal set of permissions for kro to manage its own
+  #                resources, and additionally aggregates ClusterRoles with the
+  #                label `rbac.kro.run/aggregate-to-controller: "true"` to allow
+  #                kro users to extend kro's permissions.
+  # Recommended setting is aggregation, but unrestricted remains the default for
+  # backwards compatibility.
+  mode: unrestricted
+
 deployment:
   # Number of replicas for the Pods to run
   replicaCount: 1
diff --git a/pkg/cel/ast/inspector.go b/pkg/cel/ast/inspector.go
index de9666e..d0e89ef 100644
--- a/pkg/cel/ast/inspector.go
+++ b/pkg/cel/ast/inspector.go
@@ -493,7 +493,7 @@ func (a *Inspector) structExpressionToString(s *exprpb.Expr_CreateStruct) string
 }
 
 func isInternalIdentifier(name string) bool {
-	return name == "__result__" || strings.HasPrefix(name, "$$")
+	return name == "@result" || strings.HasPrefix(name, "$$")
 }
 
 func isInternalFunction(name string) bool {
diff --git a/pkg/controller/instance/controller.go b/pkg/controller/instance/controller.go
index b032a16..1e33fcf 100644
--- a/pkg/controller/instance/controller.go
+++ b/pkg/controller/instance/controller.go
@@ -33,7 +33,7 @@ import (
 	"github.com/kro-run/kro/pkg/metadata"
 )
 
-// ReconcileConfig holds configuration parameters for the recnociliation process.
+// ReconcileConfig holds configuration parameters for the reconciliation process.
 // It allows the customization of various aspects of the controller's behavior.
 type ReconcileConfig struct {
 	// DefaultRequeueDuration is the default duration to wait before requeueing a
@@ -128,7 +128,7 @@ func (c *Controller) Reconcile(ctx context.Context, req ctrl.Request) error {
 	}
 
 	// This is one of the main reasons why we're splitting the controller into
-	// two parts. The instanciator is responsible for creating a new runtime
+	// two parts. The instantiator is responsible for creating a new runtime
 	// instance of the resource graph definition. The instance graph reconciler is responsible
 	// for reconciling the instance and its sub-resources, while keeping the same
 	// runtime object in it's fields.
diff --git a/pkg/controller/resourcegraphdefinition/controller.go b/pkg/controller/resourcegraphdefinition/controller.go
index 6fd9dda..6879d76 100644
--- a/pkg/controller/resourcegraphdefinition/controller.go
+++ b/pkg/controller/resourcegraphdefinition/controller.go
@@ -72,7 +72,7 @@ func NewResourceGraphDefinitionReconciler(
 		allowCRDDeletion:  allowCRDDeletion,
 		crdManager:        crdWrapper,
 		dynamicController: dynamicController,
-		metadataLabeler:   metadata.NewKroMetaLabeler("0.2.1", "kro-pod"),
+		metadataLabeler:   metadata.NewKROMetaLabeler(),
 		rgBuilder:         builder,
 	}
 }
diff --git a/pkg/controller/resourcegraphdefinition/controller_cleanup.go b/pkg/controller/resourcegraphdefinition/controller_cleanup.go
index 3080606..06ab81d 100644
--- a/pkg/controller/resourcegraphdefinition/controller_cleanup.go
+++ b/pkg/controller/resourcegraphdefinition/controller_cleanup.go
@@ -42,7 +42,7 @@ func (r *ResourceGraphDefinitionReconciler) cleanupResourceGraphDefinition(ctx c
 
 	group := rgd.Spec.Schema.Group
 	if group == "" {
-		group = v1alpha1.KroDomainName
+		group = v1alpha1.KRODomainName
 	}
 	// cleanup CRD
 	crdName := extractCRDName(group, rgd.Spec.Schema.Kind)
@@ -78,7 +78,7 @@ func (r *ResourceGraphDefinitionReconciler) cleanupResourceGraphDefinitionCRD(ct
 }
 
 // extractCRDName generates the CRD name from a given kind by converting it to plural form
-// and appending the Kro domain name.
+// and appending the kro domain name.
 func extractCRDName(group, kind string) string {
 	return fmt.Sprintf("%s.%s",
 		flect.Pluralize(strings.ToLower(kind)),
diff --git a/pkg/dynamiccontroller/dynamic_controller.go b/pkg/dynamiccontroller/dynamic_controller.go
index 24439b0..e929478 100644
--- a/pkg/dynamiccontroller/dynamic_controller.go
+++ b/pkg/dynamiccontroller/dynamic_controller.go
@@ -38,7 +38,7 @@
 //
 // Why not use k8s.io/controller-runtime:
 //
-//  1. Staticc nature: controller-runtime is optimized for statically defined
+//  1. Static nature: controller-runtime is optimized for statically defined
 //     controllers, however kro requires runtime creation and management
 //     of controllers for various GVRs.
 //
@@ -90,7 +90,7 @@ type Config struct {
 	// will be retried before being dropped.
 	//
 	// NOTE(a-hilaly): I'm not very sure how useful is this, i'm trying to avoid
-	// situations where reconcile errors exauhst the queue.
+	// situations where reconcile errors exhaust the queue.
 	QueueMaxRetries int
 	// ShutdownTimeout is the maximum duration to wait for the controller to
 	// gracefully shutdown. We ideally want to avoid forceful shutdowns, giving
diff --git a/pkg/graph/builder.go b/pkg/graph/builder.go
index 9de7b04..8e44262 100644
--- a/pkg/graph/builder.go
+++ b/pkg/graph/builder.go
@@ -67,7 +67,7 @@ func NewBuilder(
 //
 // The GraphBuild performs several key functions:
 //
-//	  1/ It validates the resource deinitions and their naming conventions.
+//	  1/ It validates the resource definitions and their naming conventions.
 //	  2/ It interacts with the API Server to retrieve the OpenAPI schema for the
 //	     resources, and validates the resources against the schema.
 //	  3/ Extracts and processes the CEL expressions from the resources definitions.
@@ -249,10 +249,10 @@ func (b *Builder) NewResourceGraphDefinition(originalCR *v1alpha1.ResourceGraphD
 
 // buildRGResource builds a resource from the given resource definition.
 // It provides a high-level understanding of the resource, by extracting the
-// OpenAPI schema, emualting the resource and extracting the cel expressions
+// OpenAPI schema, emulating the resource and extracting the cel expressions
 // from the schema.
 func (b *Builder) buildRGResource(rgResource *v1alpha1.Resource, namespacedResources map[k8sschema.GroupKind]bool, order int) (*Resource, error) {
-	// 1. We need to unmashal the resource into a map[string]interface{} to
+	// 1. We need to unmarshal the resource into a map[string]interface{} to
 	//    make it easier to work with.
 	resourceObject := map[string]interface{}{}
 	err := yaml.UnmarshalStrict(rgResource.Template.Raw, &resourceObject)
diff --git a/pkg/graph/crd/crd.go b/pkg/graph/crd/crd.go
index cbcccdd..beda3bd 100644
--- a/pkg/graph/crd/crd.go
+++ b/pkg/graph/crd/crd.go
@@ -29,7 +29,7 @@ import (
 func SynthesizeCRD(group, apiVersion, kind string, spec, status extv1.JSONSchemaProps, statusFieldsOverride bool) *extv1.CustomResourceDefinition {
 	crdGroup := group
 	if crdGroup == "" {
-		crdGroup = v1alpha1.KroDomainName
+		crdGroup = v1alpha1.KRODomainName
 	}
 	return newCRD(crdGroup, apiVersion, kind, newCRDSchema(spec, status, statusFieldsOverride))
 }
diff --git a/pkg/graph/dag/dag.go b/pkg/graph/dag/dag.go
index c7cd1a2..a873d42 100644
--- a/pkg/graph/dag/dag.go
+++ b/pkg/graph/dag/dag.go
@@ -26,7 +26,7 @@ import (
 type Vertex struct {
 	// ID is a unique identifier for the node
 	ID string
-	// Order records the original order, and is used to preserve the original user-provided ordering as far as posible.
+	// Order records the original order, and is used to preserve the original user-provided ordering as far as possible.
 	Order int
 	// DependsOn stores the IDs of the nodes that this node depends on.
 	// If we depend on another vertex, we must appear after that vertex in the topological sort.
diff --git a/pkg/graph/parser/cel.go b/pkg/graph/parser/cel.go
index 907ff53..4066b7f 100644
--- a/pkg/graph/parser/cel.go
+++ b/pkg/graph/parser/cel.go
@@ -24,7 +24,8 @@ const (
 	exprEnd   = "}"
 )
 
-var ErrNestedExpression = errors.New("nested expressions are not allowed")
+// Allow nested expressions, but only if they are escaped with quotes ${outer("${inner}")} is allowed, but ${outer(${inner})} is not
+var ErrNestedExpression = errors.New("nested expressions are not allowed unless inside string literals")
 
 // extractExpressions extracts all non-nested CEL expressions from a string.
 // It returns an error if it encounters a nested expression.
@@ -42,26 +43,48 @@ func extractExpressions(str string) ([]string, error) {
 		// Adjust the start index to the actual position in the string
 		startIdx += start
 
-		// We need to find the matching end bracket. we have to be careful about
-		// nested expressions and dictionary building expressions. For example:
-		// a user can have an expression like "${{"key": 123}}". For this reason,
-		// we need to keep track of the bracket count and make sure that we only
-		// consider the outermost expression
+		// We need to find the matching end bracket, being careful about
+		// nested expressions, dictionary building expressions, and string literals
 		bracketCount := 1
 		endIdx := startIdx + len(exprStart)
+		inStringLiteral := false
+		escapeNext := false
+
 		for endIdx < len(str) {
-			if str[endIdx] == '{' {
-				bracketCount++
-			} else if str[endIdx] == '}' {
-				bracketCount--
-				// If we have reached the end of the expression, break
-				if bracketCount == 0 {
-					break
+			c := str[endIdx]
+
+			// Handle escape sequences inside string literals
+			if escapeNext {
+				escapeNext = false
+				endIdx++
+				continue
+			}
+
+			// Check for escape character inside string literals
+			if inStringLiteral && c == '\\' {
+				escapeNext = true
+				endIdx++
+				continue
+			}
+
+			// Handle string literal boundaries
+			if c == '"' {
+				inStringLiteral = !inStringLiteral
+			} else if !inStringLiteral {
+				// Only count braces when not inside a string literal
+				if c == '{' {
+					bracketCount++
+				} else if c == '}' {
+					bracketCount--
+					if bracketCount == 0 {
+						break
+					}
+				} else if endIdx+1 < len(str) && str[endIdx:endIdx+2] == "${" {
+					// Allow nested expressions, but only if they are escaped with quotes
+					if str[endIdx-1] != '"' {
+						return nil, ErrNestedExpression
+					}
 				}
-			} else if endIdx+1 < len(str) && str[endIdx:endIdx+2] == "${" {
-				// We do not allow nested expressions. I'm not sure if this is a
-				// good idea, but its sounds like a reasonable restriction.
-				return nil, ErrNestedExpression
 			}
 			endIdx++
 		}
diff --git a/pkg/graph/parser/parser.go b/pkg/graph/parser/parser.go
index 6e4a9ef..557d6e0 100644
--- a/pkg/graph/parser/parser.go
+++ b/pkg/graph/parser/parser.go
@@ -68,59 +68,94 @@ func parseResource(resource interface{}, schema *spec.Schema, path string) ([]va
 }
 
 func getExpectedTypes(schema *spec.Schema) ([]string, error) {
-	// Handle "x-kubernetes-preserve-unknown-fields" extension first
-	if hasStructuralSchemaMarkerEnabled(schema, xKubernetesPreserveUnknownFields) {
+	// Handle extensions (like x-kubernetes-int-or-string)
+	if types, found := handleSchemaExtensions(schema); found {
+		return types, nil
+	}
+
+	// Handle composite schemas (like OneOf, AnyOf)
+	if types, found := handleCompositeSchemas(schema); found {
+		return types, nil
+	}
+
+	// Handle direct type definitions
+	if len(schema.Type) > 0 && schema.Type[0] != "" {
+		return schema.Type, nil
+	}
+
+	// Handle additional properties
+	if schema.AdditionalProperties != nil && schema.AdditionalProperties.Allows {
+		// NOTE(a-hilaly): I don't like the type "any", we might want to change this to "object"
+		// in the future; just haven't really thought about it yet.
+		// Basically "any" means that the field can be of any type, and we have to check
+		// the ExpectedSchema field.
 		return []string{schemaTypeAny}, nil
 	}
 
+	return nil, fmt.Errorf("unknown schema type")
+}
+
+// handleSchemaExtensions processes Kubernetes-specific schema extensions
+// and returns appropriate types if extensions are present.
+func handleSchemaExtensions(schema *spec.Schema) ([]string, bool) {
+	// Handle "x-kubernetes-preserve-unknown-fields" extension
+	if hasStructuralSchemaMarkerEnabled(schema, xKubernetesPreserveUnknownFields) {
+		return []string{schemaTypeAny}, true
+	}
+
 	// Handle "x-kubernetes-int-or-string" extension
 	if hasStructuralSchemaMarkerEnabled(schema, xKubernetesIntOrString) {
-		return []string{"string", "integer"}, nil
+		return []string{"string", "integer"}, true
 	}
 
+	return nil, false
+}
+
+// handleCompositeSchemas processes OneOf and AnyOf schemas
+// and returns collected types if present.
+func handleCompositeSchemas(schema *spec.Schema) ([]string, bool) {
 	// Handle OneOf schemas
 	if len(schema.OneOf) > 0 {
-		var types []string
-
-		for _, subSchema := range schema.OneOf {
-			// If there are structural constraints, inject object
-			if len(subSchema.Required) > 0 || subSchema.Not != nil {
-				if !slices.Contains(types, "object") {
-					types = append(types, "object")
-				}
-			}
-			// Collect types if present
-			if len(subSchema.Type) > 0 {
-				types = append(types, subSchema.Type...)
-			}
-		}
-		// If we found any types, return them
+		types := collectTypesFromSubSchemas(schema.OneOf)
 		if len(types) > 0 {
-			return types, nil
+			return types, true
 		}
 	}
 
 	// Handle AnyOf schemas
 	if len(schema.AnyOf) > 0 {
-		var types []string
-		for _, subType := range schema.AnyOf {
-			types = append(types, subType.Type...)
+		types := collectTypesFromSubSchemas(schema.AnyOf)
+		if len(types) > 0 {
+			return types, true
 		}
-		return types, nil
 	}
 
-	if len(schema.Type) > 0 && schema.Type[0] != "" {
-		return schema.Type, nil
-	}
+	return nil, false
+}
 
-	if schema.AdditionalProperties != nil && schema.AdditionalProperties.Allows {
-		// NOTE(a-hilaly): I don't like the type "any", we might want to change this to "object"
-		// in the future; just haven't really thought about it yet.
-		// Basically "any" means that the field can be of any type, and we have to check
-		// the ExpectedSchema field.
-		return []string{schemaTypeAny}, nil
+// collectTypesFromSubSchemas extracts types from a slice of schemas,
+// handling structural constraints like Required and Not.
+func collectTypesFromSubSchemas(subSchemas []spec.Schema) []string {
+	var types []string
+
+	for _, subSchema := range subSchemas {
+		// If there are structural constraints, inject object type
+		if len(subSchema.Required) > 0 || subSchema.Not != nil {
+			if !slices.Contains(types, "object") {
+				types = append(types, "object")
+			}
+		}
+		// Collect types if present
+		if len(subSchema.Type) > 0 {
+			for _, t := range subSchema.Type {
+				if t != "" && !slices.Contains(types, t) {
+					types = append(types, t)
+				}
+			}
+		}
 	}
-	return nil, fmt.Errorf("unknown schema type")
+
+	return types
 }
 
 func validateSchema(schema *spec.Schema, path string) error {
@@ -236,7 +271,7 @@ func parseScalarTypes(field interface{}, _ *spec.Schema, path string, expectedTy
 	// perform type checks for scalar types
 	switch {
 	case slices.Contains(expectedTypes, "number"):
-		if _, ok := field.(float64); !ok {
+		if !isNumber(field) {
 			return nil, fmt.Errorf("expected number type for path %s, got %T", path, field)
 		}
 	case slices.Contains(expectedTypes, "int"), slices.Contains(expectedTypes, "integer"):
@@ -286,9 +321,22 @@ func getArrayItemSchema(schema *spec.Schema, path string) (*spec.Schema, error)
 	return nil, fmt.Errorf("invalid array schema for path %s: neither Items.Schema nor Properties are defined", path)
 }
 
+func isNumber(v interface{}) bool {
+	return isInteger(v) || isFloat(v)
+}
+
+func isFloat(v interface{}) bool {
+	switch v.(type) {
+	case float32, float64:
+		return true
+	default:
+		return false
+	}
+}
+
 func isInteger(v interface{}) bool {
 	switch v.(type) {
-	case int, int64, int32:
+	case int, int8, int32, int64:
 		return true
 	default:
 		return false
diff --git a/pkg/graph/resource.go b/pkg/graph/resource.go
index 8c39c0e..e2c6fb3 100644
--- a/pkg/graph/resource.go
+++ b/pkg/graph/resource.go
@@ -21,7 +21,6 @@ import (
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	"k8s.io/kube-openapi/pkg/validation/spec"
 
-	rgschema "github.com/kro-run/kro/pkg/graph/schema"
 	"github.com/kro-run/kro/pkg/graph/variable"
 )
 
@@ -154,11 +153,6 @@ func (r *Resource) GetIncludeWhenExpressions() []string {
 	return r.includeWhenExpressions
 }
 
-// GetTopLevelFields returns the top-level fields of the resource.
-func (r *Resource) GetTopLevelFields() []string {
-	return rgschema.GetResourceTopLevelFieldNames(r.schema)
-}
-
 // IsNamespaced returns true if the resource is namespaced.
 func (r *Resource) IsNamespaced() bool {
 	return r.namespaced
diff --git a/pkg/graph/schema/schema.go b/pkg/graph/schema/schema.go
deleted file mode 100644
index beaf179..0000000
--- a/pkg/graph/schema/schema.go
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2025 The Kube Resource Orchestrator Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License"). You may
-// not use this file except in compliance with the License. A copy of the
-// License is located at
-//
-//	http://www.apache.org/licenses/LICENSE-2.0
-//
-// or in the "license" file accompanying this file. This file is distributed
-// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
-// express or implied. See the License for the specific language governing
-// permissions and limitations under the License.
-package schema
-
-import (
-	"slices"
-
-	"k8s.io/kube-openapi/pkg/validation/spec"
-)
-
-// GetResourceTopLevelFieldNames returns the top level field names
-// of the resource. It walks through the schema of the resource and
-// retrieves the top level fields including spec, status, metadata,
-// etc.
-//
-// It is up to the caller to sort filter the field names they want.
-func GetResourceTopLevelFieldNames(schema *spec.Schema) []string {
-	fieldNames := []string{}
-	if schema == nil || schema.Properties == nil {
-		return fieldNames
-	}
-	for fieldName := range schema.Properties {
-		if fieldName != "apiVersion" && fieldName != "kind" {
-			fieldNames = append(fieldNames, fieldName)
-		}
-	}
-
-	slices.Sort(fieldNames)
-	return fieldNames
-}
diff --git a/pkg/metadata/finalizers.go b/pkg/metadata/finalizers.go
index 3d0a5d2..77c10ea 100644
--- a/pkg/metadata/finalizers.go
+++ b/pkg/metadata/finalizers.go
@@ -22,21 +22,21 @@ import (
 	"github.com/kro-run/kro/api/v1alpha1"
 )
 
-const kroFinalizer = v1alpha1.KroDomainName + "/finalizer"
+const kroFinalizer = v1alpha1.KRODomainName + "/finalizer"
 
-// SetResourceGraphDefinitionFinalizer adds the Kro finalizer to the object if it's not already present.
+// SetResourceGraphDefinitionFinalizer adds the kro finalizer to the object if it's not already present.
 func SetResourceGraphDefinitionFinalizer(obj metav1.Object) {
 	if !HasResourceGraphDefinitionFinalizer(obj) {
 		obj.SetFinalizers(append(obj.GetFinalizers(), kroFinalizer))
 	}
 }
 
-// RemoveResourceGraphDefinitionFinalizer removes the Kro finalizer from the object.
+// RemoveResourceGraphDefinitionFinalizer removes the kro finalizer from the object.
 func RemoveResourceGraphDefinitionFinalizer(obj metav1.Object) {
 	obj.SetFinalizers(removeString(obj.GetFinalizers(), kroFinalizer))
 }
 
-// HasResourceGraphDefinitionFinalizer checks if the object has the Kro finalizer.
+// HasResourceGraphDefinitionFinalizer checks if the object has the kro finalizer.
 func HasResourceGraphDefinitionFinalizer(obj metav1.Object) bool {
 	return containsString(obj.GetFinalizers(), kroFinalizer)
 }
diff --git a/pkg/metadata/groupversion.go b/pkg/metadata/groupversion.go
index 89c0f00..2cebe49 100644
--- a/pkg/metadata/groupversion.go
+++ b/pkg/metadata/groupversion.go
@@ -24,7 +24,7 @@ import (
 )
 
 const (
-	KroInstancesGroupSuffix = v1alpha1.KroDomainName
+	KROInstancesGroupSuffix = v1alpha1.KRODomainName
 )
 
 // ExtractGVKFromUnstructured extracts the GroupVersionKind from an unstructured object.
diff --git a/pkg/metadata/labels.go b/pkg/metadata/labels.go
index 34b106e..eba0787 100644
--- a/pkg/metadata/labels.go
+++ b/pkg/metadata/labels.go
@@ -17,45 +17,46 @@ import (
 	"errors"
 	"fmt"
 
-	"github.com/kro-run/kro/api/v1alpha1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	"github.com/kro-run/kro/api/v1alpha1"
+	"github.com/kro-run/kro/pkg"
 )
 
 const (
-	// LabelKro is the label key used to identify Kro owned resources.
-	LabelKroPrefix = v1alpha1.KroDomainName + "/"
+	// LabelKROPrefix is the label key prefix used to identify KRO owned resources.
+	LabelKROPrefix = v1alpha1.KRODomainName + "/"
 )
 
 const (
-	NodeIDLabel = LabelKroPrefix + "node-id"
+	NodeIDLabel = LabelKROPrefix + "node-id"
 
-	OwnedLabel           = LabelKroPrefix + "owned"
-	KroVersionLabel      = LabelKroPrefix + "kro-version"
-	ControllerPodIDLabel = LabelKroPrefix + "controller-pod-id"
+	OwnedLabel      = LabelKROPrefix + "owned"
+	KROVersionLabel = LabelKROPrefix + "kro-version"
 
-	InstanceIDLabel        = LabelKroPrefix + "instance-id"
-	InstanceLabel          = LabelKroPrefix + "instance-name"
-	InstanceNamespaceLabel = LabelKroPrefix + "instance-namespace"
+	InstanceIDLabel        = LabelKROPrefix + "instance-id"
+	InstanceLabel          = LabelKROPrefix + "instance-name"
+	InstanceNamespaceLabel = LabelKROPrefix + "instance-namespace"
 
-	ResourceGraphDefinitionIDLabel        = LabelKroPrefix + "resource-graph-definition-id"
-	ResourceGraphDefinitionNameLabel      = LabelKroPrefix + "resource-graph-definition-name"
-	ResourceGraphDefinitionNamespaceLabel = LabelKroPrefix + "resource-graph-definition-namespace"
-	ResourceGraphDefinitionVersionLabel   = LabelKroPrefix + "resource-graph-definition-version"
+	ResourceGraphDefinitionIDLabel        = LabelKROPrefix + "resource-graph-definition-id"
+	ResourceGraphDefinitionNameLabel      = LabelKROPrefix + "resource-graph-definition-name"
+	ResourceGraphDefinitionNamespaceLabel = LabelKROPrefix + "resource-graph-definition-namespace"
+	ResourceGraphDefinitionVersionLabel   = LabelKROPrefix + "resource-graph-definition-version"
 )
 
-// IsKroOwned returns true if the resource is owned by Kro.
-func IsKroOwned(meta metav1.ObjectMeta) bool {
+// IsKROOwned returns true if the resource is owned by KRO.
+func IsKROOwned(meta metav1.ObjectMeta) bool {
 	v, ok := meta.Labels[OwnedLabel]
 	return ok && booleanFromString(v)
 }
 
-// SetKroOwned sets the OwnedLabel to true on the resource.
-func SetKroOwned(meta metav1.ObjectMeta) {
+// SetKROOwned sets the OwnedLabel to true on the resource.
+func SetKROOwned(meta metav1.ObjectMeta) {
 	setLabel(&meta, OwnedLabel, stringFromBoolean(true))
 }
 
-// SetKroUnowned sets the OwnedLabel to false on the resource.
-func SetKroUnowned(meta metav1.ObjectMeta) {
+// SetKROUnowned sets the OwnedLabel to false on the resource.
+func SetKROUnowned(meta metav1.ObjectMeta) {
 	setLabel(&meta, OwnedLabel, stringFromBoolean(false))
 }
 
@@ -115,8 +116,8 @@ func (gl GenericLabeler) Copy() map[string]string {
 // ResourceGraphDefinitionLabel and ResourceGraphDefinitionIDLabel labels on a resource.
 func NewResourceGraphDefinitionLabeler(rgMeta metav1.Object) GenericLabeler {
 	return map[string]string{
-		ResourceGraphDefinitionIDLabel:        string(rgMeta.GetUID()),
-		ResourceGraphDefinitionNameLabel:      rgMeta.GetName(),
+		ResourceGraphDefinitionIDLabel:   string(rgMeta.GetUID()),
+		ResourceGraphDefinitionNameLabel: rgMeta.GetName(),
 	}
 }
 
@@ -131,21 +132,17 @@ func NewInstanceLabeler(instanceMeta metav1.Object) GenericLabeler {
 	}
 }
 
-// NewKroMetaLabeler returns a new labeler that sets the OwnedLabel,
-// KroVersion, and ControllerPodID labels on a resource.
-func NewKroMetaLabeler(
-	kroVersion string,
-	controllerPodID string,
-) GenericLabeler {
+// NewKROMetaLabeler returns a new labeler that sets the OwnedLabel,
+// KROVersion, and ControllerPodID labels on a resource.
+func NewKROMetaLabeler() GenericLabeler {
 	return map[string]string{
-		OwnedLabel:           "true",
-		KroVersionLabel:      kroVersion,
-		ControllerPodIDLabel: controllerPodID,
+		OwnedLabel:      "true",
+		KROVersionLabel: pkg.Version,
 	}
 }
 
 func booleanFromString(s string) bool {
-	// for the sake of simplicy we'll avoid doing any kind
+	// for the sake of simplicity we'll avoid doing any kind
 	// of parsing here. Since those labels are set by the controller
 	// it self. We'll expect the same values back.
 	return s == "true"
diff --git a/pkg/runtime/interfaces.go b/pkg/runtime/interfaces.go
index 281d9fe..21e3fda 100644
--- a/pkg/runtime/interfaces.go
+++ b/pkg/runtime/interfaces.go
@@ -109,9 +109,6 @@ type ResourceDescriptor interface {
 	// be evaluated before deciding whether to create a resource
 	GetIncludeWhenExpressions() []string
 
-	// GetTopLevelFields returns the list of top-level fields in the resource.
-	// e.g spec, status, metadata, etc.
-	GetTopLevelFields() []string
 
 	// IsNamespaced returns true if the resource is namespaced, and false if it's
 	// cluster-scoped.
diff --git a/pkg/simpleschema/markers.go b/pkg/simpleschema/markers.go
index 9eaf3dd..d422b48 100644
--- a/pkg/simpleschema/markers.go
+++ b/pkg/simpleschema/markers.go
@@ -46,11 +46,16 @@ const (
 	MarkerTypeDefault MarkerType = "default"
 	// MarkerTypeDescription represents the `description` marker.
 	MarkerTypeDescription MarkerType = "description"
+	// MarkerTypeMinimum represents the `minimum` marker.
+	MarkerTypeMinimum MarkerType = "minimum"
+	// MarkerTypeMaximum represents the `maximum` marker.
+	MarkerTypeMaximum MarkerType = "maximum"
 )
 
 func markerTypeFromString(s string) (MarkerType, error) {
 	switch MarkerType(s) {
-	case MarkerTypeRequired, MarkerTypeDefault, MarkerTypeDescription:
+	case MarkerTypeRequired, MarkerTypeDefault, MarkerTypeDescription,
+		MarkerTypeMinimum, MarkerTypeMaximum:
 		return MarkerType(s), nil
 	default:
 		return "", fmt.Errorf("unknown marker type: %s", s)
diff --git a/pkg/simpleschema/transform.go b/pkg/simpleschema/transform.go
index df12c58..7218e76 100644
--- a/pkg/simpleschema/transform.go
+++ b/pkg/simpleschema/transform.go
@@ -15,6 +15,7 @@ package simpleschema
 
 import (
 	"fmt"
+	"strconv"
 
 	extv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 )
@@ -203,6 +204,14 @@ func (tf *transformer) applyMarkers(schema *extv1.JSONSchemaProps, markers []*Ma
 			schema.Default = &extv1.JSON{Raw: defaultValue}
 		case MarkerTypeDescription:
 			schema.Description = marker.Value
+		case MarkerTypeMinimum:
+			if val, err := strconv.ParseFloat(marker.Value, 64); err == nil {
+				schema.Minimum = &val
+			}
+		case MarkerTypeMaximum:
+			if val, err := strconv.ParseFloat(marker.Value, 64); err == nil {
+				schema.Maximum = &val
+			}
 		}
 	}
 }
diff --git a/pkg/version.go b/pkg/version.go
new file mode 100644
index 0000000..a909fe4
--- /dev/null
+++ b/pkg/version.go
@@ -0,0 +1,24 @@
+// Copyright 2025 The Kube Resource Orchestrator Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"). You may
+// not use this file except in compliance with the License. A copy of the
+// License is located at
+//
+//	http://www.apache.org/licenses/LICENSE-2.0
+//
+// or in the "license" file accompanying this file. This file is distributed
+// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+// express or implied. See the License for the specific language governing
+// permissions and limitations under the License.
+
+package pkg
+
+// These variables are populated by the go compiler using -ldflags
+var (
+	// Version is the version of the kro binary.
+	Version string // -X github.com/your/repo/pkg.Version=$(VERSION)
+	// GitCommit is the git commit that was compiled.
+	GitCommit string // -X github.com/your/repo/pkg.GitCommit=$(GIT_COMMIT)
+	// BuildDate is the date that the kro binary was built.
+	BuildDate string // -X github.com/your/repo/pkg.BuildDate=$(BUILD_DATE)
+)
diff --git a/website/Dockerfile b/website/Dockerfile
deleted file mode 100644
index 6b554ff..0000000
--- a/website/Dockerfile
+++ /dev/null
@@ -1,7 +0,0 @@
-FROM --platform=linux/amd64 node
-
-COPY . .
-
-RUN npm install
-
-RUN npm run build
\ No newline at end of file
diff --git a/website/README.md b/website/README.md
index 0c6c2c2..629b3e8 100644
--- a/website/README.md
+++ b/website/README.md
@@ -3,13 +3,17 @@
 This website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator.
 
 ### Installation
+If you're using other tools than `npm`, you can refer to the `docusaurs documentation` for build and installation https://docusaurus.io/docs/installation#build
+
+If you are using `yarn`, use:
 
 ```
 $ yarn
 ```
-
 ### Local Development
 
+To start a local development server, use:
+
 ```
 $ yarn start
 ```
@@ -18,6 +22,8 @@ This command starts a local development server and opens up a browser window. Mo
 
 ### Build
 
+To generate static content for deployment, use:
+
 ```
 $ yarn build
 ```
diff --git a/website/docs/docs/concepts/20-access-control.md b/website/docs/docs/concepts/20-access-control.md
new file mode 100644
index 0000000..198555e
--- /dev/null
+++ b/website/docs/docs/concepts/20-access-control.md
@@ -0,0 +1,75 @@
+---
+sidebar_position: 20
+---
+
+# Access Control
+
+There are currently two modes of access control supported by **kro**, if you
+[install through the Helm chart](../getting-started/01-Installation.md#install-kro-using-helm):
+
+- `unrestricted`
+
+- `aggregation`
+
+The mode is selected with a `values` property `rbac.mode`, and defaults to `unrestricted`.
+
+## `unrestricted` Access
+
+In the `unrestricted` access mode, the chart includes a `ClusterRole` granting
+**kro** _full control to every resource type in your cluster_. This can be
+useful for experimenting in a test environment, where access control is not
+necessary, but is not recommended in a production environment.
+
+In this mode, anyone with access to create `ResourceGraphDefinition` resources,
+effectively also has admin access to the cluster.
+
+## `aggregation` Access
+
+In the `aggreagation` access mode, the chart includes an [_aggregated_ `ClusterRole`](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#aggregated-clusterroles)
+which dynamically includes all rules from all `ClusterRoles` that have the label
+`rbac.kro.run/aggregate-to-controller: "true"`.
+
+There is a very minimal set of permissions provisioned by the chart itself, just
+enough to let **kro** run at all: full permissions for `ResourceGraphDefinition`s
+and its subresources, and full permissions for `CustomResourceDefinitions` as
+**kro** will create them in response to the existence of an RGD.
+
+However, this does _not_ automatically set up permissions for **kro** to actually
+reconcile those generated CRDs! In other words, when using this mode, you will
+need to provision additional access for **kro** for every new resource type you
+define.
+
+### Example
+
+If you want to create a `ResourceGraphDefinition` that specifies a new resource
+type with `kind: Foo`, and where the graph includes an `apps/v1/Deployment` and
+a `v1/ConfigMap`, you will need to create the following `ClusterRole` to ensure
+**kro** has enough access to reconcile your resources:
+
+```yaml
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  labels:
+    rbac.kro.run/aggregate-to-controller: "true"
+  name: kro:controller:foos
+rules:
+  - apiGroups:
+      - kro.run
+    resources:
+      - foos
+    verbs:
+      - "*"
+  - apiGroups:
+      - apps
+    resources:
+      - deployments
+    verbs:
+      - "*"
+  - apiGroups:
+      - ""
+    resources:
+      - configmaps
+    verbs:
+      - "*"
+```
diff --git a/website/docusaurus.config.ts b/website/docusaurus.config.ts
index e461742..1d0d6d9 100644
--- a/website/docusaurus.config.ts
+++ b/website/docusaurus.config.ts
@@ -32,7 +32,23 @@ const config: Config = {
     defaultLocale: "en",
     locales: ["en"],
   },
-
+  headTags: [
+    {
+      tagName: "meta",
+      attributes: {
+        name: "go-import",
+        content: "kro.run/pkg git https://github.com/kro-run/kro",
+      },
+    },
+    {
+      tagName: "meta",
+      attributes: {
+        name: "go-source",
+        content: "kro.run/pkg git https://github.com/kro-run/kro https://github.com/kro-run/kro/tree/main{/dir} https://github.com/kro-run/kro/blob/main{/dir}/{file}#L{line}",
+      },
+    },
+  ],
+  
   presets: [
     [
       "classic",
@@ -129,6 +145,10 @@ const config: Config = {
               label: "Tutorial",
               to: "/docs/overview",
             },
+            {
+              label: "Examples",
+              to: "/examples/",
+            },
           ],
         },
         {
@@ -138,6 +158,10 @@ const config: Config = {
               label: "Slack",
               href: "https://kubernetes.slack.com/archives/C081TMY9D6Y",
             },
+            {
+              label: "Contribution Guide",
+              href: "https://github.com/kro-run/kro/blob/main/CONTRIBUTING.md",
+            },
           ],
         },
         {
@@ -147,6 +171,10 @@ const config: Config = {
               label: "GitHub",
               href: "https://github.com/kro-run/kro",
             },
+            {
+              label: "YouTube",
+              href: "https://www.youtube.com/channel/UCUlcI3NYq9ehl5wsdfbJzSA",
+            },
           ],
         },
       ],
diff --git a/website/package-lock.json b/website/package-lock.json
index acadfa7..32bdd0a 100644
--- a/website/package-lock.json
+++ b/website/package-lock.json
@@ -794,25 +794,25 @@
       }
     },
     "node_modules/@babel/helpers": {
-      "version": "7.26.0",
-      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz",
-      "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==",
+      "version": "7.26.10",
+      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.10.tgz",
+      "integrity": "sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g==",
       "license": "MIT",
       "dependencies": {
-        "@babel/template": "^7.25.9",
-        "@babel/types": "^7.26.0"
+        "@babel/template": "^7.26.9",
+        "@babel/types": "^7.26.10"
       },
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/parser": {
-      "version": "7.26.2",
-      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz",
-      "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==",
+      "version": "7.26.10",
+      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.10.tgz",
+      "integrity": "sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA==",
       "license": "MIT",
       "dependencies": {
-        "@babel/types": "^7.26.0"
+        "@babel/types": "^7.26.10"
       },
       "bin": {
         "parser": "bin/babel-parser.js"
@@ -2063,9 +2063,9 @@
       }
     },
     "node_modules/@babel/runtime": {
-      "version": "7.26.0",
-      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz",
-      "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==",
+      "version": "7.26.10",
+      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.10.tgz",
+      "integrity": "sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==",
       "license": "MIT",
       "dependencies": {
         "regenerator-runtime": "^0.14.0"
@@ -2075,9 +2075,9 @@
       }
     },
     "node_modules/@babel/runtime-corejs3": {
-      "version": "7.26.0",
-      "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.26.0.tgz",
-      "integrity": "sha512-YXHu5lN8kJCb1LOb9PgV6pvak43X2h4HvRApcN5SdWeaItQOzfn1hgP6jasD6KWQyJDBxrVmA9o9OivlnNJK/w==",
+      "version": "7.26.10",
+      "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.26.10.tgz",
+      "integrity": "sha512-uITFQYO68pMEYR46AHgQoyBg7KPPJDAbGn4jUTIRgCFJIp88MIBUianVOplhZDEec07bp9zIyr4Kp0FCyQzmWg==",
       "license": "MIT",
       "dependencies": {
         "core-js-pure": "^3.30.2",
@@ -2088,14 +2088,14 @@
       }
     },
     "node_modules/@babel/template": {
-      "version": "7.25.9",
-      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz",
-      "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==",
+      "version": "7.26.9",
+      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz",
+      "integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==",
       "license": "MIT",
       "dependencies": {
-        "@babel/code-frame": "^7.25.9",
-        "@babel/parser": "^7.25.9",
-        "@babel/types": "^7.25.9"
+        "@babel/code-frame": "^7.26.2",
+        "@babel/parser": "^7.26.9",
+        "@babel/types": "^7.26.9"
       },
       "engines": {
         "node": ">=6.9.0"
@@ -2120,9 +2120,9 @@
       }
     },
     "node_modules/@babel/types": {
-      "version": "7.26.0",
-      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz",
-      "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==",
+      "version": "7.26.10",
+      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.10.tgz",
+      "integrity": "sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==",
       "license": "MIT",
       "dependencies": {
         "@babel/helper-string-parser": "^7.25.9",
@@ -12577,9 +12577,9 @@
       }
     },
     "node_modules/prismjs": {
-      "version": "1.29.0",
-      "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz",
-      "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==",
+      "version": "1.30.0",
+      "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz",
+      "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==",
       "license": "MIT",
       "engines": {
         "node": ">=6"



Test patch:
diff --git a/pkg/graph/crd/crd_test.go b/pkg/graph/crd/crd_test.go
new file mode 100644
index 0000000..87bcddc
--- /dev/null
+++ b/pkg/graph/crd/crd_test.go
@@ -0,0 +1,236 @@
+// Copyright 2025 The Kube Resource Orchestrator Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"). You may
+// not use this file except in compliance with the License. A copy of the
+// License is located at
+//
+//	http://www.apache.org/licenses/LICENSE-2.0
+//
+// or in the "license" file accompanying this file. This file is distributed
+// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+// express or implied. See the License for the specific language governing
+// permissions and limitations under the License.
+
+package crd
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+	extv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
+
+	"github.com/kro-run/kro/api/v1alpha1"
+)
+
+func TestSynthesizeCRD(t *testing.T) {
+	tests := []struct {
+		name                 string
+		group                string
+		apiVersion           string
+		kind                 string
+		spec                 extv1.JSONSchemaProps
+		status               extv1.JSONSchemaProps
+		statusFieldsOverride bool
+		expectedName         string
+		expectedGroup        string
+	}{
+		{
+			name:                 "standard group and kind",
+			group:                "kro.com",
+			apiVersion:           "v1",
+			kind:                 "Widget",
+			spec:                 extv1.JSONSchemaProps{Type: "object"},
+			status:               extv1.JSONSchemaProps{Type: "object"},
+			statusFieldsOverride: true,
+			expectedName:         "widgets.kro.com",
+			expectedGroup:        "kro.com",
+		},
+		{
+			name:                 "empty group uses default domain",
+			group:                "",
+			apiVersion:           "v1alphav2",
+			kind:                 "Service",
+			spec:                 extv1.JSONSchemaProps{Type: "object"},
+			status:               extv1.JSONSchemaProps{Type: "object"},
+			statusFieldsOverride: false,
+			expectedName:         "services." + v1alpha1.KRODomainName,
+			expectedGroup:        v1alpha1.KRODomainName,
+		},
+		{
+			name:                 "mixes case kind",
+			group:                "kro.com",
+			apiVersion:           "v2",
+			kind:                 "DataBase",
+			spec:                 extv1.JSONSchemaProps{Type: "object"},
+			status:               extv1.JSONSchemaProps{Type: "object"},
+			statusFieldsOverride: true,
+			expectedName:         "databases.kro.com",
+			expectedGroup:        "kro.com",
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			crd := SynthesizeCRD(tt.group, tt.apiVersion, tt.kind, tt.spec, tt.status, tt.statusFieldsOverride)
+
+			assert.Equal(t, tt.expectedName, crd.Name)
+			assert.Equal(t, tt.expectedGroup, crd.Spec.Group)
+			assert.Equal(t, tt.kind, crd.Spec.Names.Kind)
+			assert.Equal(t, tt.kind+"List", crd.Spec.Names.ListKind)
+
+			require.Len(t, crd.Spec.Versions, 1)
+			version := crd.Spec.Versions[0]
+			assert.Equal(t, tt.apiVersion, version.Name)
+			assert.True(t, version.Served)
+			assert.True(t, version.Storage)
+
+			require.NotNil(t, version.Schema)
+			require.NotNil(t, version.Schema.OpenAPIV3Schema)
+
+			require.NotNil(t, version.Subresources)
+			require.NotNil(t, version.Subresources.Status)
+		})
+	}
+}
+
+func TestNewCRD(t *testing.T) {
+	tests := []struct {
+		name             string
+		group            string
+		apiVersion       string
+		kind             string
+		expectedName     string
+		expectedKind     string
+		expectedPlural   string
+		expectedSingular string
+	}{
+		{
+			name:             "basic example",
+			group:            "kro.com",
+			apiVersion:       "v1",
+			kind:             "Test",
+			expectedName:     "tests.kro.com",
+			expectedKind:     "Test",
+			expectedPlural:   "tests",
+			expectedSingular: "test",
+		},
+		{
+			name:             "uppercase kind",
+			group:            "kro.com",
+			apiVersion:       "v2beta1",
+			kind:             "CONFIG",
+			expectedName:     "configs.kro.com",
+			expectedKind:     "CONFIG",
+			expectedPlural:   "configs",
+			expectedSingular: "config",
+		},
+		{
+			name:             "mixed case kind",
+			group:            "kro.com",
+			apiVersion:       "v2beta1",
+			kind:             "WebHook",
+			expectedName:     "webhooks.kro.com",
+			expectedKind:     "WebHook",
+			expectedPlural:   "webhooks",
+			expectedSingular: "webhook",
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			schema := &extv1.JSONSchemaProps{Type: "object"}
+			crd := newCRD(tt.group, tt.apiVersion, tt.kind, schema)
+
+			assert.Equal(t, tt.expectedName, crd.Name)
+			assert.Equal(t, tt.group, crd.Spec.Group)
+			assert.Equal(t, tt.expectedKind, crd.Spec.Names.Kind)
+			assert.Equal(t, tt.expectedKind+"List", crd.Spec.Names.ListKind)
+			assert.Equal(t, tt.expectedPlural, crd.Spec.Names.Plural)
+			assert.Equal(t, tt.expectedSingular, crd.Spec.Names.Singular)
+
+			assert.Equal(t, extv1.NamespaceScoped, crd.Spec.Scope)
+
+			require.Len(t, crd.Spec.Versions, 1)
+			assert.Equal(t, tt.apiVersion, crd.Spec.Versions[0].Name)
+			assert.Equal(t, schema, crd.Spec.Versions[0].Schema.OpenAPIV3Schema)
+
+			assert.Nil(t, crd.OwnerReferences)
+		})
+	}
+}
+
+func TestNewCRDSchema(t *testing.T) {
+	tests := []struct {
+		name                    string
+		spec                    extv1.JSONSchemaProps
+		status                  extv1.JSONSchemaProps
+		statusFieldsOverride    bool
+		expectedStateField      bool
+		expectedConditionsField bool
+	}{
+		{
+			name:                    "with override enabled and empty status",
+			spec:                    extv1.JSONSchemaProps{Type: "object"},
+			status:                  extv1.JSONSchemaProps{Type: "object"},
+			statusFieldsOverride:    true,
+			expectedStateField:      true,
+			expectedConditionsField: true,
+		},
+		{
+			name:                    "with override disabled",
+			spec:                    extv1.JSONSchemaProps{Type: "object"},
+			status:                  extv1.JSONSchemaProps{Type: "object"},
+			statusFieldsOverride:    false,
+			expectedStateField:      false,
+			expectedConditionsField: false,
+		},
+		{
+			name: "with existing status properties and override enabled",
+			spec: extv1.JSONSchemaProps{Type: "object"},
+			status: extv1.JSONSchemaProps{Type: "object", Properties: map[string]extv1.JSONSchemaProps{
+				"state": {
+					Type:        "string",
+					Description: "Custom state filed",
+				},
+				"customField": {Type: "string"},
+			}},
+			statusFieldsOverride:    true,
+			expectedStateField:      true,
+			expectedConditionsField: true,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			schema := newCRDSchema(tt.spec, tt.status, tt.statusFieldsOverride)
+
+			assert.Equal(t, "object", schema.Type)
+			require.NotNil(t, schema.Properties)
+
+			assert.Contains(t, schema.Properties, "apiVersion")
+			assert.Contains(t, schema.Properties, "kind")
+			assert.Contains(t, schema.Properties, "metadata")
+			assert.Contains(t, schema.Properties, "spec")
+			assert.Contains(t, schema.Properties, "status")
+
+			assert.Equal(t, tt.spec, schema.Properties["spec"])
+
+			statusProps := schema.Properties["status"]
+			require.NotNil(t, statusProps.Properties)
+
+			if tt.expectedStateField {
+				assert.Contains(t, statusProps.Properties, "state")
+				assert.Equal(t, defaultConditionsType, statusProps.Properties["conditions"])
+			}
+
+			if tt.status.Properties != nil {
+				if customField, exists := tt.status.Properties["customField"]; exists {
+					assert.Contains(t, statusProps.Properties, "customField")
+					assert.Equal(t, customField, statusProps.Properties["customField"])
+				}
+			}
+
+		})
+	}
+}
diff --git a/pkg/graph/parser/cel_test.go b/pkg/graph/parser/cel_test.go
index 1bb10aa..10f7602 100644
--- a/pkg/graph/parser/cel_test.go
+++ b/pkg/graph/parser/cel_test.go
@@ -114,6 +114,24 @@ func TestExtractExpressions(t *testing.T) {
 			want:    nil,
 			wantErr: true,
 		},
+		{
+			name:    "Nested expression but with quotes",
+			input:   "${outer(\"${inner}\")}",
+			want:    []string{"outer(\"${inner}\")"},
+			wantErr: false,
+		},
+		{
+			name:    "Nested closing brace without opening one",
+			input:   "${\"text with }} inside\"}",
+			want:    []string{"\"text with }} inside\""},
+			wantErr: false,
+		},
+		{
+			name:    "Nested open brace without closing one",
+			input:   "${\"text with { inside\"}",
+			want:    []string{"\"text with { inside\""},
+			wantErr: false,
+		},
 		{
 			name:    "Expressions with dictionary building",
 			input:   "${true ? {'key': 'value'} : {'key': 'value2'}}",
@@ -197,6 +215,9 @@ func TestIsOneShotExpression(t *testing.T) {
 		{"With newlines", "${resource.list.map(\n  x,\n  x * 2\n)}", true, false},
 		{"Complex expression", "${resource.list.map(x, x.field).filter(y, y > 5)}", true, false},
 		{"Nested expression (should error)", "${outer(${inner})}", false, true},
+		{"Nested expression but with quotes", "${outer(\"${inner}\")}", true, false},
+		{"Nested closing brace without opening one", "${\"text with }} inside\"}", true, false},
+		{"Nested open brace without closing one", "${\"text with { inside\"}", true, false},
 	}
 
 	for _, tt := range tests {
diff --git a/pkg/graph/parser/parser_test.go b/pkg/graph/parser/parser_test.go
index a02615f..2518af6 100644
--- a/pkg/graph/parser/parser_test.go
+++ b/pkg/graph/parser/parser_test.go
@@ -1196,6 +1196,18 @@ func TestOneOfWithStructuralConstraints(t *testing.T) {
 		if !reflect.DeepEqual(expressions[0].Path, expected.Path) {
 			t.Errorf("Expected path %s, got %s", expected.Path, expressions[0].Path)
 		}
+
+		if !reflect.DeepEqual(expressions[0].Expressions, expected.Expressions) {
+			t.Errorf("Expressions mismatch: got %v, want %v", expressions[0].Expressions, expected.Expressions)
+		}
+
+		if !reflect.DeepEqual(expressions[0].ExpectedTypes, expected.ExpectedTypes) {
+			t.Errorf("ExpectedTypes mismatch: got %v, want %v", expressions[0].ExpectedTypes, expected.ExpectedTypes)
+		}
+
+		if expressions[0].StandaloneExpression != expected.StandaloneExpression {
+			t.Errorf("StandaloneExpression mismatch: got %v, want %v", expressions[0].StandaloneExpression, expected.StandaloneExpression)
+		}
 	})
 
 	t.Run("networkRef with external reference", func(t *testing.T) {
@@ -1284,6 +1296,10 @@ func TestOneOfWithStructuralConstraints(t *testing.T) {
 		if !reflect.DeepEqual(expressions[0].ExpectedTypes, expected.ExpectedTypes) {
 			t.Errorf("Expected types %v, got %v", expected.ExpectedTypes, expressions[0].ExpectedTypes)
 		}
+
+		if expressions[0].StandaloneExpression != expected.StandaloneExpression {
+			t.Errorf("StandaloneExpression mismatch: got %v, want %v", expressions[0].StandaloneExpression, expected.StandaloneExpression)
+		}
 	})
 }
 
@@ -1495,3 +1511,83 @@ func TestPreserveUnknownFields(t *testing.T) {
 		})
 	}
 }
+
+func TestCollectTypesFromSubSchemas(t *testing.T) {
+	testCases := []struct {
+		name       string
+		subSchemas []spec.Schema
+		wantTypes  []string
+	}{
+		{
+			name: "simple types without constraints",
+			subSchemas: []spec.Schema{
+				{SchemaProps: spec.SchemaProps{Type: []string{"string"}}},
+				{SchemaProps: spec.SchemaProps{Type: []string{"integer"}}},
+			},
+			wantTypes: []string{"string", "integer"},
+		},
+		{
+			name: "with Required constraint",
+			subSchemas: []spec.Schema{
+				{SchemaProps: spec.SchemaProps{
+					Type:     []string{"string"},
+					Required: []string{"field"},
+				}},
+			},
+			wantTypes: []string{"object", "string"},
+		},
+		{
+			name: "with Not constraint",
+			subSchemas: []spec.Schema{
+				{SchemaProps: spec.SchemaProps{
+					Type: []string{"string"},
+					Not: &spec.Schema{
+						SchemaProps: spec.SchemaProps{Type: []string{"integer"}},
+					},
+				}},
+			},
+			wantTypes: []string{"object", "string"},
+		},
+		{
+			name: "with both Required and Not constraints",
+			subSchemas: []spec.Schema{
+				{SchemaProps: spec.SchemaProps{
+					Type:     []string{"string"},
+					Required: []string{"field"},
+					Not: &spec.Schema{
+						SchemaProps: spec.SchemaProps{Type: []string{"integer"}},
+					},
+				}},
+			},
+			wantTypes: []string{"object", "string"},
+		},
+		{
+			name: "duplicate types",
+			subSchemas: []spec.Schema{
+				{SchemaProps: spec.SchemaProps{Type: []string{"string"}}},
+				{SchemaProps: spec.SchemaProps{Type: []string{"string"}}},
+				{SchemaProps: spec.SchemaProps{
+					Type:     []string{"string"},
+					Required: []string{"field"},
+				}},
+			},
+			wantTypes: []string{"object", "string"},
+		},
+		{
+			name: "empty type",
+			subSchemas: []spec.Schema{
+				{SchemaProps: spec.SchemaProps{Type: []string{""}}},
+			},
+			wantTypes: []string{},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			gotTypes := collectTypesFromSubSchemas(tc.subSchemas)
+			if !areEqualSlices(gotTypes, tc.wantTypes) {
+				t.Errorf("collectTypesFromSubSchemas() = %v, want %v", gotTypes, tc.wantTypes)
+			}
+		})
+	}
+}
diff --git a/pkg/metadata/labels_test.go b/pkg/metadata/labels_test.go
index 6798d08..fe174d5 100644
--- a/pkg/metadata/labels_test.go
+++ b/pkg/metadata/labels_test.go
@@ -30,19 +30,19 @@ func (m *mockObject) GetObjectMeta() metav1.Object {
 	return m
 }
 
-func TestIsKroOwned(t *testing.T) {
+func TestIsKROOwned(t *testing.T) {
 	cases := []struct {
 		name     string
 		labels   map[string]string
 		expected bool
 	}{
 		{
-			name:     "owned by Kro",
+			name:     "owned by kro",
 			labels:   map[string]string{OwnedLabel: "true"},
 			expected: true,
 		},
 		{
-			name:     "not owned by Kro",
+			name:     "not owned by kro",
 			labels:   map[string]string{OwnedLabel: "false"},
 			expected: false,
 		},
@@ -56,13 +56,13 @@ func TestIsKroOwned(t *testing.T) {
 	for _, tc := range cases {
 		t.Run(tc.name, func(t *testing.T) {
 			meta := metav1.ObjectMeta{Labels: tc.labels}
-			result := IsKroOwned(meta)
+			result := IsKROOwned(meta)
 			assert.Equal(t, tc.expected, result)
 		})
 	}
 }
 
-func TestSetKroOwned(t *testing.T) {
+func TestSetKROOwned(t *testing.T) {
 	cases := []struct {
 		name          string
 		initialLabels map[string]string
@@ -83,13 +83,13 @@ func TestSetKroOwned(t *testing.T) {
 	for _, tc := range cases {
 		t.Run(tc.name, func(t *testing.T) {
 			meta := metav1.ObjectMeta{Labels: tc.initialLabels}
-			SetKroOwned(meta)
+			SetKROOwned(meta)
 			assert.Equal(t, tc.expected, meta.Labels)
 		})
 	}
 }
 
-func TestSetKroUnowned(t *testing.T) {
+func TestSetKROUnowned(t *testing.T) {
 	cases := []struct {
 		name          string
 		initialLabels map[string]string
@@ -110,7 +110,7 @@ func TestSetKroUnowned(t *testing.T) {
 	for _, tc := range cases {
 		t.Run(tc.name, func(t *testing.T) {
 			meta := metav1.ObjectMeta{Labels: tc.initialLabels}
-			SetKroUnowned(meta)
+			SetKROUnowned(meta)
 			assert.Equal(t, tc.expected, meta.Labels)
 		})
 	}
diff --git a/pkg/runtime/runtime_test.go b/pkg/runtime/runtime_test.go
index b042935..d30b0b7 100644
--- a/pkg/runtime/runtime_test.go
+++ b/pkg/runtime/runtime_test.go
@@ -2644,9 +2644,6 @@ func (m *mockResource) GetIncludeWhenExpressions() []string {
 	return m.conditions
 }
 
-func (m *mockResource) GetTopLevelFields() []string {
-	return m.topLevelFields
-}
 
 func (m *mockResource) IsNamespaced() bool {
 	return m.namespaced
diff --git a/pkg/simpleschema/field_test.go b/pkg/simpleschema/field_test.go
index a8f8984..33840a5 100644
--- a/pkg/simpleschema/field_test.go
+++ b/pkg/simpleschema/field_test.go
@@ -51,6 +51,26 @@ func TestParseFieldSchema(t *testing.T) {
 			wantMarkers: nil,
 			wantErr:     true,
 		},
+		{
+			name:        "integer field with min and max",
+			fieldSchema: "integer | minimum=0 maximum=100",
+			wantType:    "integer",
+			wantMarkers: []*Marker{
+				{MarkerType: MarkerTypeMinimum, Key: "minimum", Value: "0"},
+				{MarkerType: MarkerTypeMaximum, Key: "maximum", Value: "100"},
+			},
+			wantErr: false,
+		},
+		{
+			name:        "number field with decimal constraints",
+			fieldSchema: "float | minimum=0.1 maximum=1.0",
+			wantType:    "float",
+			wantMarkers: []*Marker{
+				{MarkerType: MarkerTypeMinimum, Key: "minimum", Value: "0.1"},
+				{MarkerType: MarkerTypeMaximum, Key: "maximum", Value: "1.0"},
+			},
+			wantErr: false,
+		},
 	}
 
 	for _, tt := range tests {
diff --git a/pkg/simpleschema/markers_test.go b/pkg/simpleschema/markers_test.go
index cacf57f..0b63d26 100644
--- a/pkg/simpleschema/markers_test.go
+++ b/pkg/simpleschema/markers_test.go
@@ -86,6 +86,24 @@ func TestParseMarkers(t *testing.T) {
 			},
 			wantErr: false,
 		},
+		{
+			name:  "minimum and maximum markers",
+			input: "minimum=0 maximum=100",
+			want: []*Marker{
+				{MarkerType: MarkerTypeMinimum, Key: "minimum", Value: "0"},
+				{MarkerType: MarkerTypeMaximum, Key: "maximum", Value: "100"},
+			},
+			wantErr: false,
+		},
+		{
+			name:  "decimal minimum and maximum",
+			input: "minimum=0.1 maximum=1.5",
+			want: []*Marker{
+				{MarkerType: MarkerTypeMinimum, Key: "minimum", Value: "0.1"},
+				{MarkerType: MarkerTypeMaximum, Key: "maximum", Value: "1.5"},
+			},
+			wantErr: false,
+		},
 		{
 			name:  "Markers with spaces in values",
 			input: "description=\"This has spaces\" default=5 required=true",
diff --git a/test/integration/suites/ackekscluster/generator.go b/test/integration/suites/ackekscluster/generator.go
index 6fc0fad..e9bcba2 100644
--- a/test/integration/suites/ackekscluster/generator.go
+++ b/test/integration/suites/ackekscluster/generator.go
@@ -66,7 +66,7 @@ func eksCluster(
 	instanceGenerator := func(namespace, name, version string) *unstructured.Unstructured {
 		return &unstructured.Unstructured{
 			Object: map[string]interface{}{
-				"apiVersion": fmt.Sprintf("%s/%s", krov1alpha1.KroDomainName, "v1alpha1"),
+				"apiVersion": fmt.Sprintf("%s/%s", krov1alpha1.KRODomainName, "v1alpha1"),
 				"kind":       "EKSCluster",
 				"metadata": map[string]interface{}{
 					"name":      name,
diff --git a/test/integration/suites/core/conditional_test.go b/test/integration/suites/core/conditional_test.go
index 945bc38..1a14cb3 100644
--- a/test/integration/suites/core/conditional_test.go
+++ b/test/integration/suites/core/conditional_test.go
@@ -243,7 +243,7 @@ var _ = Describe("Conditions", func() {
 		// Create instance
 		instance := &unstructured.Unstructured{
 			Object: map[string]interface{}{
-				"apiVersion": fmt.Sprintf("%s/%s", krov1alpha1.KroDomainName, "v1alpha1"),
+				"apiVersion": fmt.Sprintf("%s/%s", krov1alpha1.KRODomainName, "v1alpha1"),
 				"kind":       "TestConditions",
 				"metadata": map[string]interface{}{
 					"name":      name,
diff --git a/test/integration/suites/core/lifecycle_test.go b/test/integration/suites/core/lifecycle_test.go
index 83706ae..8e3c260 100644
--- a/test/integration/suites/core/lifecycle_test.go
+++ b/test/integration/suites/core/lifecycle_test.go
@@ -108,7 +108,7 @@ var _ = Describe("Update", func() {
 		// Create initial instance
 		instance := &unstructured.Unstructured{
 			Object: map[string]interface{}{
-				"apiVersion": fmt.Sprintf("%s/%s", krov1alpha1.KroDomainName, "v1alpha1"),
+				"apiVersion": fmt.Sprintf("%s/%s", krov1alpha1.KRODomainName, "v1alpha1"),
 				"kind":       "TestUpdate",
 				"metadata": map[string]interface{}{
 					"name":      "test-instance-for-updates",
diff --git a/test/integration/suites/core/readiness_test.go b/test/integration/suites/core/readiness_test.go
index 6fba093..786345c 100644
--- a/test/integration/suites/core/readiness_test.go
+++ b/test/integration/suites/core/readiness_test.go
@@ -181,7 +181,7 @@ var _ = Describe("Readiness", func() {
 		// Create instance
 		instance := &unstructured.Unstructured{
 			Object: map[string]interface{}{
-				"apiVersion": fmt.Sprintf("%s/%s", krov1alpha1.KroDomainName, "v1alpha1"),
+				"apiVersion": fmt.Sprintf("%s/%s", krov1alpha1.KRODomainName, "v1alpha1"),
 				"kind":       "TestReadiness",
 				"metadata": map[string]interface{}{
 					"name":      name,
diff --git a/test/integration/suites/core/recover_test.go b/test/integration/suites/core/recover_test.go
index 115591f..9632cb3 100644
--- a/test/integration/suites/core/recover_test.go
+++ b/test/integration/suites/core/recover_test.go
@@ -196,7 +196,7 @@ var _ = Describe("Recovery", func() {
 		name := "test-recovery"
 		instance := &unstructured.Unstructured{
 			Object: map[string]interface{}{
-				"apiVersion": fmt.Sprintf("%s/%s", krov1alpha1.KroDomainName, "v1alpha1"),
+				"apiVersion": fmt.Sprintf("%s/%s", krov1alpha1.KRODomainName, "v1alpha1"),
 				"kind":       "TestRecovery",
 				"metadata": map[string]interface{}{
 					"name":      name,
diff --git a/test/integration/suites/core/validation_test.go b/test/integration/suites/core/validation_test.go
index 5084b84..7b9f835 100644
--- a/test/integration/suites/core/validation_test.go
+++ b/test/integration/suites/core/validation_test.go
@@ -285,6 +285,7 @@ var _ = Describe("Validation", func() {
 				"123Test",       // Starts with number
 				"Test.Resource", // Contains dot
 				"Test!",         // Special character
+				"TestThisIsAValidButReallyLongNameSoLongThatItIsGreaterThan63Characters", // Greater than 63 characters
 			}
 
 			for _, kind := range invalidKinds {
@@ -298,15 +299,7 @@ var _ = Describe("Validation", func() {
 					),
 				)
 
-				Expect(env.Client.Create(ctx, rgd)).To(Succeed())
-
-				Eventually(func(g Gomega) {
-					err := env.Client.Get(ctx, types.NamespacedName{
-						Name: rgd.Name,
-					}, rgd)
-					g.Expect(err).ToNot(HaveOccurred())
-					g.Expect(rgd.Status.State).To(Equal(krov1alpha1.ResourceGraphDefinitionStateInactive))
-				}, 10*time.Second, time.Second).Should(Succeed())
+				Expect(env.Client.Create(ctx, rgd)).ToNot(Succeed())
 			}
 		})
 	})
diff --git a/test/integration/suites/deploymentservice/generator.go b/test/integration/suites/deploymentservice/generator.go
index 447efd5..d034b3b 100644
--- a/test/integration/suites/deploymentservice/generator.go
+++ b/test/integration/suites/deploymentservice/generator.go
@@ -46,7 +46,7 @@ func deploymentService(
 	instanceGenerator := func(namespace, name string, port int) *unstructured.Unstructured {
 		return &unstructured.Unstructured{
 			Object: map[string]interface{}{
-				"apiVersion": fmt.Sprintf("%s/%s", krov1alpha1.KroDomainName, "v1alpha1"),
+				"apiVersion": fmt.Sprintf("%s/%s", krov1alpha1.KRODomainName, "v1alpha1"),
 				"kind":       "DeploymentService",
 				"metadata": map[string]interface{}{
 					"name":      name,
diff --git a/test/integration/suites/networkingstack/generator.go b/test/integration/suites/networkingstack/generator.go
index dbb63fd..6f8d7ca 100644
--- a/test/integration/suites/networkingstack/generator.go
+++ b/test/integration/suites/networkingstack/generator.go
@@ -53,7 +53,7 @@ func networkingStack(
 	instanceGenerator := func(namespace, name string) *unstructured.Unstructured {
 		return &unstructured.Unstructured{
 			Object: map[string]interface{}{
-				"apiVersion": fmt.Sprintf("%s/%s", krov1alpha1.KroDomainName, "v1alpha1"),
+				"apiVersion": fmt.Sprintf("%s/%s", krov1alpha1.KRODomainName, "v1alpha1"),
 				"kind":       "NetworkingStack",
 				"metadata": map[string]interface{}{
 					"name":      name,