Skip to main content

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Important

  • Never git push to main branch, always open a PR.
  • Webresearch, today is 2025

Workspace Structure

IMPORTANT: This directory IS the portal-workspace repository itself!

It serves as a parent/workspace repository that:

  • Contains shared documentation and configuration
  • Has other repositories cloned inside it (app-portal, templates, etc.)
  • These nested repositories are gitignored and managed independently
open-service-portal/         # THIS directory = portal-workspace repo
├── .git/ # portal-workspace git (own repository)
├── CLAUDE.md # Workspace-level context (this file)
├── README.md # Workspace overview
├── docs/ # Shared documentation
│ ├── annotations.md # Annotation namespace strategy ⭐ NEW
│ ├── crossplane-v2-architecture.md # Crossplane v2 overview
│ ├── crossplane-catalog-setup.md # Template management
│ └── local-kubernetes-setup.md # K8s setup guide
├── scripts/ # Unified setup and utility scripts
│ ├── cluster-setup.sh # Universal K8s cluster setup
│ ├── cluster-config.sh # Auto-detect cluster and configure
│ ├── cluster-cleanup.sh # Remove all platform components
│ ├── cluster-kubeconfig.sh # Extract and manage kubeconfig files
│ ├── template-status.sh # Check template releases and PRs
│ ├── template-reload.sh # Reload templates in cluster
│ ├── template-release.sh # Automate template releases to GitHub
│ ├── repos-sync.sh # Sync all nested repositories
│ ├── manifests/setup/ # Infrastructure manifests
│ │ ├── crossplane-functions.yaml # Composition functions
│ │ ├── crossplane-provider-*.yaml # Provider definitions
│ │ ├── external-dns.yaml # External-DNS with CRDs
│ │ └── flux-catalog.yaml # Catalog watcher
│ └── manifests/config/ # Environment configs
│ ├── environment-configs.yaml
│ └── flux-catalog-orders.yaml
├── .gitignore # Ignores nested repos below

├── ingestor/ # Standalone ingestor plugin (workspace-level)
│ ├── src/ # TypeScript source code
│ ├── templates/ # ⭐ Plugin default Handlebars templates
│ │ ├── backstage/ # Main template structure (default.hbs, debug.hbs)
│ │ ├── parameters/ # Parameter generation templates
│ │ ├── steps/ # Workflow step templates (default, gitops)
│ │ ├── output/ # Output section templates
│ │ └── api/ # API documentation templates
│ ├── scripts/ # CLI wrapper scripts
│ ├── tests/ # Test suite with assertions
│ └── docs/ # Comprehensive documentation
├── app-portal/ # NESTED repo - Main Backstage application
│ ├── ingestor-templates/ # ⭐⭐ CUSTOMIZED templates (override plugin defaults)
│ │ ├── backstage/ # Customized main template (default.hbs, debug.hbs)
│ │ ├── parameters/ # Customized parameter generation
│ │ ├── steps/ # Customized workflows (default, gitops)
│ │ ├── output/ # Customized output messages
│ │ ├── api/ # Customized API documentation
│ │ └── README.md # Customization guide
│ ├── packages/ # Frontend and backend packages
│ ├── plugins/ # Custom plugins (scaffolder, ingestor)
│ ├── app-config.yaml # Legacy monolithic configuration
│ └── app-config/ # Modular configuration directory
│ ├── auth.yaml # Authentication providers
│ ├── backend.yaml # Backend service settings
│ ├── catalog.yaml # Software catalog configuration
│ ├── ingestor.yaml # Ingestor plugins configuration
│ ├── integrations.yaml # SCM integrations
│ ├── kubernetes.yaml # Kubernetes clusters
│ ├── scaffolder.yaml # Scaffolder settings
│ └── techdocs.yaml # TechDocs configuration
├── catalog/ # NESTED repo - Template registry for Flux
│ └── templates/ # Template references (XRDs/Compositions)
├── catalog-orders/ # NESTED repo - XR instances from Backstage
│ └── (structure managed by template publishPhase)
├── concepts/ # Architecture and design documentation
├── deploy-app-portal/ # NESTED repo - Kubernetes deployment manifests

├── template-dns-record/ # NESTED repo - Mock DNS template
│ └── configuration/
│ └── xrd.yaml # XRD with openportal.dev/tags label
├── template-cloudflare-dnsrecord/ # NESTED repo - DNS management using External-DNS
│ ├── xrd.yaml # XRD with publishPhase annotations
│ ├── composition.yaml # Pipeline mode composition
│ └── examples/ # XR examples
├── template-whoami/ # NESTED repo - Demo application template
│ ├── xrd.yaml # Simple app deployment XRD
│ └── composition.yaml # Helm + DNS composition
├── template-whoami-service/ # NESTED repo - Composite service template
│ ├── xrd.yaml # Composition of compositions
│ └── composition.yaml # Combines whoami + DNS

├── service-nodejs-template/ # NESTED repo - Node.js service template
├── service-mongodb-template/ # NESTED repo - MongoDB service template
├── service-mongodb-golden-path-template/ # NESTED repo - MongoDB with best practices
├── service-firewall-template/ # NESTED repo - Firewall rules template
├── service-dnsrecord-template/ # NESTED repo - DNS record service
├── service-cluster-template/ # NESTED repo - Cluster provisioning

├── backstage/ # LOCAL CLONE - Backstage core docs (gitignored)
├── backstage-community-plugins/ # LOCAL CLONE - Community plugins (gitignored)
└── scripts/ # Unified setup and utility scripts

## Setup

To set up the complete workspace, clone all repositories:

```bash
# Clone the workspace
git clone https://github.com/open-service-portal/portal-workspace.git

# Navigate to the workspace
cd portal-workspace

# Clone the app-portal code repository inside the workspace
git clone https://github.com/open-service-portal/app-portal.git

# Clone the standalone ingestor plugin into app-portal plugins directory
cd app-portal/plugins
git clone https://github.com/open-service-portal/ingestor.git

GitHub Organization

Repository Overview

Core Application

  • app-portal/ - Main Backstage application (git@github.com:open-service-portal/app-portal.git)
    • Scaffolded with @backstage/create-app
    • Contains frontend and backend packages
    • Configured for GitHub/GitLab integration

Backstage Plugins

  • ingestor/ - Standalone Kubernetes resource discovery plugin (git@github.com:open-service-portal/ingestor.git)
    • Automatically discovers and imports K8s resources into catalog
    • Supports Crossplane XRD template generation
    • Includes CLI tools for ingestion and export operations
    • Uses unified ingestion engine for both CLI and runtime

Crossplane Templates & GitOps

  • catalog/ - Central registry for Crossplane templates/XRDs (git@github.com:open-service-portal/catalog.git)
  • catalog-orders/ - GitOps repository for XR instances created via Backstage (git@github.com:open-service-portal/catalog-orders.git)
  • template-dns-record/ - Mock DNS template for testing (git@github.com:open-service-portal/template-dns-record.git)
  • template-cloudflare-dnsrecord/ - DNS management via External-DNS and CloudflareDNSRecord XRs (git@github.com:open-service-portal/template-cloudflare-dnsrecord.git)
  • template-whoami/ - Demo application deployment (git@github.com:open-service-portal/template-whoami.git)
  • template-whoami-service/ - Composite service (app + DNS) (git@github.com:open-service-portal/template-whoami-service.git)

Backstage Templates (Services)

  • service-nodejs-template/ - Template for Node.js services (git@github.com:open-service-portal/service-nodejs-template.git)
  • service-mongodb-template/ - MongoDB database service (git@github.com:open-service-portal/service-mongodb-template.git)
  • service-mongodb-golden-path-template/ - MongoDB with best practices (git@github.com:open-service-portal/service-mongodb-golden-path-template.git)
  • service-firewall-template/ - Network firewall rules (git@github.com:open-service-portal/service-firewall-template.git)
  • service-dnsrecord-template/ - DNS record management (git@github.com:open-service-portal/service-dnsrecord-template.git)
  • service-cluster-template/ - Kubernetes cluster provisioning (git@github.com:open-service-portal/service-cluster-template.git)

Deployment & Infrastructure

  • deploy-app-portal/ - Kubernetes deployment manifests for Backstage (git@github.com:open-service-portal/deploy-app-portal.git)

Documentation & Workspace

  • portal-workspace/ - This workspace repository with documentation and setup scripts (git@github.com:open-service-portal/portal-workspace.git)
  • concepts/ - Architecture and design documentation (part of workspace)

Local Reference Repositories (gitignored)

  • backstage/ - Local clone of Backstage core for documentation reference
  • backstage-community-plugins/ - Community plugins for reference

Development

Each repository has its own CLAUDE.md file with specific development commands:

  • app-portal/CLAUDE.md - Backstage development commands, build instructions, plugin creation
  • template-*/CLAUDE.md - Template testing, scaffolding, and validation commands
  • docs/CLAUDE.md - Documentation build and preview commands

See the respective repository's CLAUDE.md for detailed instructions.

Modular Configuration

The app-portal now uses a modular configuration architecture:

# Configuration is split into focused modules
app-config/
├── auth.yaml # Authentication (GitHub, GitLab, etc.)
├── backend.yaml # Backend settings (ports, CORS, database)
├── catalog.yaml # Catalog providers and locations
├── ingestor.yaml # Kubernetes and Crossplane ingestors
├── integrations.yaml # SCM integrations
├── kubernetes.yaml # Cluster connections
├── scaffolder.yaml # Template settings
└── techdocs.yaml # Documentation platform

The start.js script automatically loads all configuration modules. See Modular Configuration Documentation for details.

Ingestor Plugin

The standalone ingestor plugin provides Kubernetes resource discovery and XRD-to-Template transformation:

⭐ Key Location: ingestor/templates/ - Handlebars templates that transform XRDs into Backstage entities

ingestor/                          # Workspace-level standalone plugin
├── templates/ # ⭐ XRD transformation templates (Handlebars)
│ ├── backstage/default.hbs # Main template - generates Template entity metadata
│ ├── backstage/debug.hbs # Debug template - shows all available variables
│ ├── parameters/default.hbs # Parameter generation (namespace, properties, etc.)
│ ├── parameters/gitops.hbs # GitOps-specific parameters (owner, repo, branch)
│ ├── steps/default.hbs # Direct workflow (kube:apply)
│ ├── steps/gitops.hbs # GitOps workflow (publish:github:pull-request)
│ ├── output/default.hbs # Output messages for users
│ ├── output/gitops.hbs # GitOps-specific outputs (PR links, etc.)
│ └── api/default.hbs # API entity generation
├── src/
│ ├── xrd-transform/ # XRD transformation engine
│ │ ├── lib/transform.ts # Core Handlebars rendering logic
│ │ └── helpers/index.ts # Helper functions (getLabel, getAnnotation, etc.)
│ ├── lib/ # Core processing engine
│ │ ├── IngestionEngine.ts
│ │ ├── ResourceValidator.ts
│ │ └── EntityBuilder.ts
│ ├── adapters/ # Environment adapters
│ ├── cli/ # CLI tools (ts-node)
│ └── module.ts # Backend module registration
├── tests/ # Comprehensive test suite
└── docs/ # Detailed documentation

Key Features:

  • Discovers XRDs from multiple clusters
  • Generates Template and API entities
  • Unified engine for CLI and runtime
  • Direct ts-node execution for development
  • Includes export and ingestion CLI tools

Template System Overview:

There are TWO template locations:

  1. Plugin Defaults: ingestor/templates/ - Base templates shipped with the plugin
  2. Customizations: app-portal/ingestor-templates/ - Project-specific overrides

Template Priority: Customized templates in app-portal/ingestor-templates/ override plugin defaults.

When to Edit Which:

  • Plugin defaults (ingestor/templates/): When fixing bugs or adding features to the core plugin
  • Customizations (app-portal/ingestor-templates/): When adapting templates for your organization

Common Template Modifications:

  1. Metadata Changes (title, description, tags, labels):

    • Plugin default: ingestor/templates/backstage/default.hbs
    • Customized: app-portal/ingestor-templates/backstage/default.hbs
    • Helper functions: getLabel, getAnnotation, slugify, extractTitle
    • Example: Adding XRD labels like openportal.dev/version to templates
  2. Parameter Changes (form fields):

    • Plugin default: ingestor/templates/parameters/default.hbs or gitops.hbs
    • Customized: app-portal/ingestor-templates/parameters/default.hbs or gitops.hbs
    • Controls what fields users see when creating resources
  3. Workflow Changes (what happens when user submits):

    • Plugin default: ingestor/templates/steps/default.hbs or gitops.hbs
    • Customized: app-portal/ingestor-templates/steps/default.hbs or gitops.hbs
    • Controls direct apply vs GitOps PR workflow
  4. After making plugin template changes:

    cd ingestor
    ./run-tests.sh # Run regression tests
    # Tests will show what changed in output
    # Edit test assertions SURGICALLY (preserve headers!)
    git diff tests/ # Verify minimal changes only
  5. After making customized template changes:

    • No tests needed (customizations are project-specific)
    • Test manually by running the app and creating resources
    • See app-portal/ingestor-templates/README.md for customization guide

See Ingestor Documentation and Ingestor CLAUDE.md for complete details.

Troubleshooting

Common issues and solutions are documented in docs/troubleshooting/.

When encountering errors, check the troubleshooting guides first.

High-Level Architecture

Technology Stack

  • Framework: Backstage (latest stable version)
  • Runtime: Node.js 20 LTS
  • Package Manager: Yarn (Berry)
  • Language: TypeScript
  • Database: SQLite (dev) / PostgreSQL (production)
  • Container: Docker / Podman
  • Orchestration: Kubernetes
  • GitOps: Flux with catalog pattern
  • Infrastructure: Crossplane v2.0 with namespaced XRs
  • Composition Functions: go-templating, patch-and-transform, auto-ready, environment-configs

Core Components

  1. Software Catalog - Track services, libraries, and components
  2. TechDocs - Documentation platform
  3. Scaffolder - Software templates for creating new services
  4. Kubernetes Plugin - K8s resource visibility
  5. Auth - GitHub/GitLab authentication

Development Guidelines

Code Style

  • Use TypeScript for all new code
  • Follow existing patterns in the codebase
  • Prefer functional components in React
  • Use Material-UI components for consistency

Template Development

Backstage Service Templates

  1. Follow the naming convention: service-{name}-template
  2. Add template.yaml in repository root
  3. Templates are auto-discovered via GitHub provider
  4. Include comprehensive documentation
  5. Add example service scaffolding in content/ directory

Crossplane Infrastructure Templates

  1. Follow the naming convention: template-{resource}
  2. Structure:
    • xrd.yaml - API definition (use v2 with namespaced scope)
    • composition.yaml - Implementation (use Pipeline mode)
    • rbac.yaml - Required permissions
    • examples/xr.yaml - Usage examples (XRs, not claims)
  3. Register in catalog repository
  4. Flux automatically syncs from catalog
  5. Use standard annotation namespaces - See Annotation Strategy

Environment Variables

Required environment variables should be documented and include:

  • GITHUB_TOKEN - GitHub Personal Access Token
  • GITLAB_TOKEN - GitLab Personal Access Token (if using GitLab)
  • Additional service-specific tokens

Important Files

Backstage Configuration

  • app-config.yaml - Base configuration
  • app-config.production.yaml - Production overrides
  • app-config.local.yaml - Local development overrides (gitignored)

Package Structure

app-portal/
├── packages/
│ ├── app/ # Frontend application
│ └── backend/ # Backend services
├── plugins/ # Custom plugins
└── app-config.yaml # Configuration

Integration Points

GitHub Integration

  • Organization: open-service-portal
  • Discovery: Automatic repository scanning
  • Templates: GitHub Actions for CI/CD

Service Provisioning

  • Crossplane v2 with namespaced XRs (no claims needed)
  • GitOps workflow with Flux catalog pattern
  • Pipeline mode compositions with functions
  • Platform-wide environment configurations

GitOps Workflow

Two repositories work together for complete GitOps:

  1. catalog/ - Contains Crossplane templates (XRDs/Compositions)

    • Watched by Flux to deploy template definitions
    • Templates define what resources CAN be created
  2. catalog-orders/ - Contains XR instances (actual resource requests)

    • Populated by Backstage when users create resources
    • Watched by Flux to deploy XR instances
    • Structure determined by template publishPhase configuration

Kubernetes Setup

We support any Kubernetes distribution with a unified setup:

Infrastructure Setup

# Universal setup script for any K8s cluster
./scripts/cluster-setup.sh

# Installs:
# - NGINX Ingress Controller
# - Flux GitOps with catalog watcher
# - Crossplane v2.0 with namespaced XRs
# - Composition functions (globally installed)
# - Base environment configurations
# - provider-kubernetes with RBAC
# - provider-helm for chart deployments
# - External-DNS for DNS management (namespace-isolated)
# - Backstage service account with token secret

Environment Configuration

# Auto-detect cluster from kubectl context and configure
./scripts/cluster-config.sh
# - Automatically detects current kubectl context
# - Extracts cluster name from context (multiple contexts can share same cluster)
# - Loads .env.${cluster} file (e.g., .env.rancher-desktop, .env.openportal)
# - Creates demo namespace for testing
# - Creates app-config.${cluster}.local.yaml for Backstage with API token
# - Configures External-DNS credentials if provided
# - Updates EnvironmentConfigs for the cluster
# - Patches Flux to watch cluster-specific paths in catalog-orders
# - Generates secure API token for programmatic Backstage access

Cluster-Based Configuration The script uses cluster names (not context names) for configuration. This allows multiple contexts with different authentication methods to share the same configuration when connecting to the same cluster:

  • Context osp-openportal (client certs) → Cluster openportal → Uses .env.openportal
  • Context osp-openportal-oidc (OIDC) → Cluster openportal → Uses .env.openportal

Backstage API Access The cluster-config.sh script generates a secure API token for programmatic access to Backstage. This token is stored in the cluster-specific configuration file (app-config.${cluster}.local.yaml) and can be used to query the Backstage catalog.

# Get all templates
./scripts/backstage-api.sh '/api/catalog/entities?filter=kind=Template'

# Get specific template
./scripts/backstage-api.sh /api/catalog/entities/by-name/template/default/your-template-name

# With jq filtering
./scripts/backstage-api.sh '/api/catalog/entities?filter=kind=Template' '.[] | .metadata.name'

See docs/backstage/api-access.md for full documentation.

The API provides read access to all catalog entities including templates, components, and systems. Remember to restart Backstage after running cluster-config.sh for the new token to take effect.

DNS Management with External-DNS We use External-DNS for unified DNS management across all providers:

# DNS records are created via CloudflareDNSRecord XRs
kubectl apply -f - <<EOF
apiVersion: openportal.dev/v1alpha1
kind: CloudflareDNSRecord
metadata:
name: my-app
namespace: my-namespace
spec:
name: my-app
type: A
value: "192.168.1.100"
ttl: 300
EOF

# The XR creates a DNSEndpoint resource that External-DNS processes
# External-DNS then creates the actual DNS record in Cloudflare

Template Management Scripts

# Check template status (releases and PRs)
./scripts/template-status.sh

# Reload all templates in the cluster (with finalizer handling)
./scripts/template-reload.sh

# Sync all nested repositories
./scripts/repos-sync.sh

# Complete cleanup of all platform components
./scripts/cluster-cleanup.sh

Environment Files Environment files are named after cluster names (not context names). Multiple contexts pointing to the same cluster share one config file:

  • .env.rancher-desktop - Rancher Desktop cluster
  • .env.docker-desktop - Docker Desktop cluster
  • .env.openportal - OpenPortal production cluster (shared by multiple contexts)
  • .env.${cluster} - Any other cluster

Copy from examples:

cp .env.rancher-desktop.example .env.rancher-desktop
vim .env.rancher-desktop

# For OpenPortal (used by both osp-openportal and osp-openportal-oidc contexts)
cp .env.openportal.example .env.openportal
vim .env.openportal

Version Philosophy: Latest + Greatest We intentionally use the latest stable versions of all components, especially Crossplane and its providers. This approach:

  • Identifies compatibility issues early - We discover breaking changes and API incompatibilities before they impact production deployments
  • Tests new features proactively - We can evaluate and adopt new capabilities like namespaced resources and v2 APIs immediately
  • Provides feedback to upstream - Early adoption helps the Crossplane community by identifying edge cases
  • Keeps our platform modern - Ensures we're not building on deprecated or soon-to-be-obsolete APIs
  • Simplifies future migrations - Incremental updates are easier than massive version jumps

This is a deliberate testing strategy for our local development environment. Production deployments should use pinned, thoroughly tested versions.

Crossplane Template Usage

# Create XRs directly in your namespace (v2 style)
kubectl apply -f - <<EOF
apiVersion: platform.io/v1alpha1
kind: XDNSRecord # Direct XR, no claim needed!
metadata:
name: my-app
namespace: my-team # Namespaced!
spec:
type: A
name: my-app
value: "192.168.1.100"
EOF

Local Backstage Reference Documentation

For efficient documentation search, we maintain local clones of key Backstage repositories:

Clone Commands

# Clone Backstage core repository (shallow clone for faster download)
git clone --depth 1 git@github.com:backstage/backstage.git

# Clone Community Plugins
git clone --depth 1 git@github.com:backstage/community-plugins.git backstage-community-plugins

Core Documentation Paths

  • Backstage Core: /backstage/docs/ - Architecture, auth providers, plugins, tutorials
  • Community Plugins: /backstage-community-plugins/docs/ - Compatibility guides, maintainer docs
  • Community Plugins List: /backstage-community-plugins/workspaces/*/ - 100+ plugin READMEs
  • Ingestor Plugin: /app-portal/plugins/ingestor/docs/ - Our Kubernetes resource discovery and ingestion plugin

Key Documentation Areas

  • Getting Started: backstage/docs/getting-started/
  • Auth Providers: backstage/docs/auth/*/provider.md
  • Plugin Development: backstage/docs/plugins/
  • Backend System: backstage/docs/backend-system/
  • Frontend System: backstage/docs/frontend-system/
  • Software Templates: backstage/docs/features/software-templates/
  • Kubernetes Integration: backstage/docs/features/kubernetes/
  • TechDocs: backstage/docs/features/techdocs/

Note: These paths are gitignored via the following patterns in .gitignore:

backstage*/
crossplane*/

Best Practices

  1. Version Control

    • Keep workspace repositories in sync
    • Use semantic versioning for releases
    • Tag stable versions
  2. Security

    • Never commit plaintext secrets or tokens
    • Use environment variables for local development
    • Follow Backstage security guidelines
    • Use RBAC for namespace isolation with XRs
  3. Documentation

    • Update TechDocs with service changes
    • Maintain up-to-date README files
    • Document architectural decisions

Development Workflow

Workflow for Changes

  1. Document concepts in workspace

    • Create markdown files in this workspace for cross-cutting concerns
    • Document architectural decisions
    • Plan major features before implementation
  2. Work in individual repositories

    • Create feature branches
    • Make changes in the appropriate repository
    • Test locally
  3. Submit pull requests

    • Push feature branch to GitHub
    • Create PR with clear description
    • Link to related issues or concepts

GitHub CLI Usage

We use GitHub CLI (gh) for GitHub operations with file-based bodies:

# Creating issues
gh issue create --repo open-service-portal/app-portal \
--title "Issue title here" \
--body-file issue-body.md

# Creating pull requests
gh pr create --repo open-service-portal/app-portal \
--title "feat: PR title here" \
--body-file pr-body.md

Conventions:

  • Use --body-file for better markdown formatting
  • Don't include the title in the markdown file (use --title flag)
  • Body files can be deleted after use

Workspace Conventions

Repository Naming

  • app-* - Applications (e.g., app-portal)
  • catalog - Crossplane template registry
  • template-* - Crossplane infrastructure templates
  • service-*-template - Backstage service templates
  • plugin-* - Shared Backstage plugins (future)

Documentation Structure

Each repository should contain:

  • README.md - Quick start and overview
  • CLAUDE.md - Detailed development instructions for Claude Code
  • docs/ - Extended documentation (if needed)

Communication & Analogies

We use restaurant industry analogies to explain technical concepts, especially for Crossplane:

  • Menu = XRD (Composite Resource Definition) - what customers can order
  • Kitchen = Composition - how to prepare what was ordered
  • Supplier = Provider (e.g., provider-helm) - source of ingredients
  • Customer = Developer using the platform
  • Order = XR - direct resource request (v2 style, no claim)
  • Soft Opening = Testing phase before production

This makes complex infrastructure concepts accessible to all stakeholders.

Key Patterns

  1. Service Discovery: Use Backstage Software Catalog
  2. Authentication: GitHub/GitLab OAuth
  3. Service Templates: Backstage Scaffolder for service creation
  4. Infrastructure Templates: Crossplane with catalog pattern
  5. Documentation: TechDocs with MkDocs
  6. Deployment: GitOps workflow with Flux
  7. Resource Management: Namespaced XRs (Crossplane v2)