Skip to content

feat: add Grant primitive XRD with polymorphic same-Org / cross-Org dispatch#9

Merged
patrickleet merged 1 commit into
mainfrom
feat/grant-xrd
May 21, 2026
Merged

feat: add Grant primitive XRD with polymorphic same-Org / cross-Org dispatch#9
patrickleet merged 1 commit into
mainfrom
feat/grant-xrd

Conversation

@patrickleet
Copy link
Copy Markdown
Contributor

@patrickleet patrickleet commented May 21, 2026

Composes 1 or 2 underlying Zitadel MRs depending on whether userOrgId matches projectOrgId. Same-Org → user.zitadel/Grant. Cross-Org → project.zitadel/Grant + user.zitadel/Grant with projectGrantId. Multi-iter gating verified.

Same-Org path live-validated on colima (composed user.zitadel/Grant; XR status mode: same-org, ready: true, userGrantId populated; clean delete cascade). Cross-Org path validated via render tests + observed-resources fixture.

8/8 KCL Grant tests pass locally; 21/21 across the repo.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added Grant functionality for managing user-to-project role assignments, supporting both same-organization and cross-organization scenarios with polymorphic dispatch.
  • Documentation

    • Updated documentation with Grant XRD details, composition behavior, and example manifests for different deployment patterns.
  • Tests

    • Added comprehensive test suite for Grant composition scenarios.

Review Change Stack

…ispatch

Second auth-group primitive that survives the "is it just a single-MR
wrapper?" test. Composes 1 or 2 underlying Zitadel MRs depending on
whether the user's home Org matches the project's enclosing Org.

Same-Org (spec.userOrgId == spec.projectOrgId): one MR
  - user.zitadel.m.crossplane.io/Grant — the user's role assignment
    within the project (no projectGrantId)

Cross-Org (spec.userOrgId != spec.projectOrgId): two MRs
  - project.zitadel.m.crossplane.io/Grant — the cross-Org Project
    Grant authorizing the role set for the user's home Org
  - user.zitadel.m.crossplane.io/Grant — the user's role assignment
    with projectGrantId pointing at the Project Grant, pulling roles
    from the granted set

Multi-iter convergence: in cross-Org mode the user/Grant emits only
once the project/Grant's atProvider.id is observed (standard composition
gating per feedback_crossplane_composition_gates).

Schema takes flat IDs (userId + userOrgId + projectId + projectOrgId +
roles[]), matching the established convention from Tenant + MachineUser
— operator copies UUIDs from the relevant XR statuses or Zitadel UI.

Two examples (same-org + cross-org); 4 KCL CompositionTests covering
same-org, cross-org iter 1, multi-role, managementPolicies propagation.
Multi-iter convergence verified via observed-resources fixture.

21/21 KCL tests pass (13 AuthStack + 4 MachineUser + 4 Grant); all 8
examples render via make render:all.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@patrickleet patrickleet merged commit 6f0af36 into main May 21, 2026
3 of 4 checks passed
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 21, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4fc5db50-e788-4910-b31d-c58f3953abc9

📥 Commits

Reviewing files that changed from the base of the PR and between 16be7c5 and de8c4d0.

📒 Files selected for processing (18)
  • .github/workflows/on-pr.yaml
  • .github/workflows/on-push-main.yaml
  • Makefile
  • README.md
  • apis/grants/composition.yaml
  • apis/grants/definition.yaml
  • examples/grants/cross-org.yaml
  • examples/grants/same-org.yaml
  • functions/grant/000-state-init.yaml.gotmpl
  • functions/grant/010-state-status.yaml.gotmpl
  • functions/grant/100-project-grant.yaml.gotmpl
  • functions/grant/200-user-grant.yaml.gotmpl
  • functions/grant/999-status.yaml.gotmpl
  • tests/test-grant/kcl.mod
  • tests/test-grant/main.k
  • tests/test-grant/model
  • tests/test-grant/observed/cross-org-iter2.yaml
  • upbound.yaml

📝 Walkthrough

Walkthrough

This PR introduces a production-ready Grant composite resource for Crossplane that enables polymorphic user-to-project role assignment within the same organization or across organizations via Zitadel's grant APIs, complete with function pipeline orchestration, examples, tests, and CI integration.

Changes

Grant Composite Resource Feature

Layer / File(s) Summary
Grant CRD definition and composition wiring
apis/grants/definition.yaml, apis/grants/composition.yaml
CompositeResourceDefinition for the Grant kind defines required org/user/project/role fields and managementPolicies in spec, plus mode/ready/grant-id tracking in status. Composition wires the Grant to the hops-ops-auth-stackgrant and crossplane-contrib-function-auto-ready functions in pipeline order.
Grant function pipeline implementation
functions/grant/000-state-init.yaml.gotmpl, functions/grant/010-state-status.yaml.gotmpl, functions/grant/100-project-grant.yaml.gotmpl, functions/grant/200-user-grant.yaml.gotmpl, functions/grant/999-status.yaml.gotmpl
Five-step pipeline: state-init computes same-org vs cross-org mode and initializes labels/config; project-grant conditionally renders Zitadel Grant only for cross-org; user-grant renders user-scoped Grant with cross-org gating on observed projectGrantId; status-compute reads Ready conditions and sets readiness conjunction; status-emit writes Grant mode and IDs back to XR status.
Grant examples and composition tests
examples/grants/same-org.yaml, examples/grants/cross-org.yaml, tests/test-grant/main.k, tests/test-grant/kcl.mod, tests/test-grant/model, tests/test-grant/observed/cross-org-iter2.yaml
Same-org and cross-org example manifests demonstrate user-to-project role binding. Four composition test cases validate polymorphic dispatch (same-org emits only user-grant, cross-org iter-1 emits only project-grant, iter-2 follows), multi-role propagation to composed roleKeys, and managementPolicies forwarding to composed user-grant; test module wires KCL package and fixtures.
CI/build integration and documentation
.github/workflows/on-pr.yaml, .github/workflows/on-push-main.yaml, Makefile, README.md, upbound.yaml
PR and push-main validation workflows, Makefile EXAMPLES list, and render:all/validate:all targets now include the two new grant examples. README status table marks Grant as complete (✓) and documents the Grant XRD's polymorphic same-org/cross-org dispatch semantics and multi-iteration behavior. Project metadata updated to describe both MachineUser and Grant patterns.

Sequence Diagram

sequenceDiagram
    participant GrantXR as Grant XR<br/>(auth.hops.ops.com.ai)
    participant StateInit as state-init<br/>(000)
    participant ProjectGrant as project-grant<br/>(100)
    participant UserGrant as user-grant<br/>(200)
    participant ZitadelAPI as Zitadel API
    participant StatusCompute as status-compute<br/>(010)
    participant StatusEmit as status-emit<br/>(999)
    GrantXR->>StateInit: spec: userOrgId, projectOrgId, userId, projectId, roles
    StateInit->>StateInit: Compute mode: userOrgId == projectOrgId ?<br/>"same-org" : "cross-org"
    StateInit-->>StateInit: $state = {mode, labels, config}
    alt cross-org mode
        StateInit->>ProjectGrant: render project.zitadel.../Grant
        ProjectGrant->>ZitadelAPI: apply project-grant (orgId, projectId, grantedOrgId)
        ZitadelAPI-->>ProjectGrant: projectGrantId, Ready status
    end
    alt same-org mode OR projectGrantId ready
        ProjectGrant->>UserGrant: (cross-org) pass observed.projectGrantId
        StateInit->>UserGrant: (same-org) render directly
        UserGrant->>ZitadelAPI: apply user-grant (userId, projectId, [projectGrantId])
        ZitadelAPI-->>UserGrant: userGrantId, Ready status
    end
    UserGrant->>StatusCompute: observed.resources = {project-grant, user-grant}
    StatusCompute->>StatusCompute: ready = all Ready conditions true
    StatusCompute-->>StatusCompute: $state.status = {ready, mode, userGrantId, projectGrantId}
    StatusCompute->>StatusEmit: emit status
    StatusEmit-->>GrantXR: status.ready, status.mode, status.userGrantId, status.projectGrantId
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

The PR introduces a substantial new composite resource with multi-mode orchestration logic, templated function implementations, and comprehensive test coverage. The core complexity lies in understanding the conditional rendering of project-grant only in cross-org mode and the multi-iteration readiness dependency. The changes are cohesive and well-scoped to a single feature, with clear separation between CRD definition, function pipeline, examples, and CI integration.

Possibly related PRs

  • hops-ops/auth-stack#6: Prior refactor of multi-API per-example configuration in .github/workflows and Makefile that established the pattern extended by this PR's new grant examples and workflow validation targets.

🐰 A grant with purpose true, in mode both old and new,
Same-org or cross, the paths align, as Zitadel grants pursue.
State flows through functions five, with polymorphic grace,
Ready conditions sing together, in every org-paired space! 🎯

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/grant-xrd

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

❤️ Share

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

@patrickleet patrickleet deleted the feat/grant-xrd branch May 21, 2026 04:44
@github-actions
Copy link
Copy Markdown

Published Crossplane Package

The following Crossplane package was published as part of this PR:

Package: ghcr.io/hops-ops/auth-stack:pr-9-14540e3ea9a3966c23faa2ace25ef2d0d7e1c91f

View Package

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant