Monorepo containing:
| Path | Contents |
|---|---|
onboarding-api/ |
Spring Boot application (Java 17, Gradle) — CX-0006 partner registration incl. credential-holder registration at the IssuerService; self-contained build |
membership-hub/ |
Spring Boot application (Java 17, Gradle) — the Catena-X Membership Hub: drives the full member journey (registration via the Onboarding API, then EDC resource provisioning via the CFM Tenant Manager); self-contained build |
compliance-tracker/ |
CFM lifecycle agent (Go) consuming lifecycle CloudEvents off NATS — self-contained module |
charts/onboarding-api/ |
Helm chart for the Onboarding API application |
charts/membership-hub/ |
Helm chart for the Membership Hub application |
charts/cx-ve/ |
Umbrella chart: the whole VE (platform, Catena-X profile, Onboarding API, Membership Hub, Certo + agent) as one release |
scripts/ |
Utility and automation scripts |
vendor-stack/ |
A reference system under test for the VE to verify: the VE's own components in a separate kind cluster, plus the vendor-side scripts (README) |
The member journey is split across the two applications, with the Membership Hub as the entry point (see membership-hub/README.md for its API and states):
POST /api/memberson the Membership Hub mints anexternalId, resolves the member's DID, and refuses a DID or BPN a live membership already holds (409).- If the VE hosts the member (no
didsupplied), the hub creates a tenant and deploys the participant profile via the CFM Tenant Manager, which runs the VPA provisioning orchestration: connector, IdentityHub, Siglet and Certo. Neither credential activity is part of the DAG. ThePOSTreturns immediately, inPROVISIONING; the deployment runs on a worker. A member that brought its own DID skips this entirely — its resources run elsewhere, already deployed by its own operator. - Once the participant context exists, the hub submits the registration to the Onboarding API in the onboarding-service-provider role (OAuth2 against the VE's OSP IdP). It runs the CX-0006 sequence synchronously — validation, BPN assignment, identity proofing — registers the member as a credential holder with the IssuerService (with the attestation properties the seeded credential definitions map from) and then has the IssuerService send it a DCP credential offer, which the member's own IdentityHub requests the credentials from.
- That order is why the deployment comes first: the offer is PUSHED to the credential service
the member's DID document advertises, so the wallet has to exist by the time the registration
runs. The Onboarding API's CONFIRMED callback therefore means "holder registered and
credentials offered", and the membership ends at
CREDENTIALS_OFFERED; the delivery itself shows up on the issuance events. GET /api/members/{externalId}on the hub returns the correlated record — the participant context id and, once the registration is under way, the registration ids — by reading the deployed profile's state from the Tenant Manager.
Offboarding does not revoke credentials. The dispose side of the orchestration used to revoke them through the CFM onboarding activity, which left with the credential activities. Nothing in the VE triggers offboarding today; whatever does it later has to revoke through the IssuerService admin API itself.
A declined registration leaves a hosted member's resources behind. Deployment precedes registration, and nothing takes the profile back when the Onboarding API declines. The hub's duplicate check catches the repeat case it can see (that is the
409above); for anything else, the orphaned profile has to be removed through the Tenant Manager by hand.
Participants get their data plane registered at provisioning time: the hub attaches the
configured transfer-type mapping (participant.ccm.* / participant.dataplane.*) to the
cfm.dataplane VPA of the participant profile, and the platform's siglet agent installs it in
Siglet and registers the data-plane instance with the control plane.
The Gradle builds live in the application directories (each its own build root — the repository root is not a Gradle project):
cd onboarding-api # same commands apply in membership-hub/
./gradlew build # compile and run tests
./gradlew bootRun # run the application locally (port 8080)
./gradlew bootBuildImage # build an OCI container image via buildpacks
docker build -t <name> . # alternative image build (layered jar Dockerfile)The Go agent in compliance-tracker/ is a separate, self-contained module — see
compliance-tracker/README.md:
cd compliance-tracker
go build ./...
make test-unit # handler tests; no Docker needed
make test # all tests; the launcher suite starts a NATS testcontainer
docker build -t compliance-tracker .The whole VE deploys as ONE umbrella release. The release name cx-ve is load-bearing — the
platform names its infra resources <release>-nats / <release>-vault / <release>-postgresql
and the checked-in values reference those names:
helm dependency update charts/cx-ve
helm lint charts/onboarding-api charts/membership-hub charts/cx-ve
helm install cx-ve charts/cx-ve -n edc-v --create-namespaceThe umbrella pulls the Core Platform Distribution, the Catena-X profile and Certo as OCI
dependencies and vendors the local charts/onboarding-api and charts/membership-hub. All
seeding runs as post-install hooks of the single release, in one ordered hook space: platform
seeds (weights 10/20) → Catena-X profile (110-130) → onboarding-api jwtlet mapping (200) →
certo jwtlet mappings (210) → certo activity/orchestration (220) → membership-hub jwtlet
mapping (230).
scripts/install-ve.sh stands up the complete VE on a kind
cluster. Run it from the repository root:
./scripts/install-ve.shPrerequisites: docker, kind, helm, kubectl, and the Traefik chart repository
(helm repo add traefik https://traefik.github.io/charts).
The script performs the following steps:
- Recreates the kind cluster
cxve(override the name with--cluster <name>; an existing cluster of that name is deleted first). The kubeconfig is written to~/.kube/cxve.config; the generated kind config maps host ports 80/443 into the node, so the gateway is reachable onhttp://localhostwithout a port-forward. - Installs Traefik (
traefik-values.yaml) and the Gateway API CRDs. - Pre-patches CoreDNS (
setup-did-dns.sh --pre) so the gateway hostnames resolve in-cluster — the release's own seed hooks already dereference them during the install. - Builds the Onboarding API, Membership Hub and Compliance Tracker images from this checkout and loads them into the cluster, so the VE always runs the local code; every other image is pulled from its registry.
- Installs the umbrella release
cx-veinto namespaceedc-v(dependencies resolved viahelm dependency update). - Re-runs
setup-did-dns.shin discovery mode: rewrites are re-derived from the deployed HTTPRoutes and verified end to end (in-cluster DNS + the issuer DID document).
For the edit-build-verify loop on an existing cluster, scripts/redeploy-ve.sh rebuilds the
three images, re-vendors the charts and upgrades the release in place.
Once complete, the APIs are reachable through the gateway: the Membership Hub at
http://cxve.localhost/hub and the Onboarding API at http://cxve.localhost/onboarding
(Swagger UIs at <prefix>/swagger). Onboard a member through the hub with
curl -s -X POST http://cxve.localhost/hub/api/members \
-H "Content-Type: application/json" \
-d '{
"name": "Acme Corp",
"shortName": "acme",
"bpn": "BPNL000000000ACM",
"uniqueIds": [ { "type": "VAT_ID", "value": "DE123456789" } ],
"companyRoles": [ "ACTIVE_PARTICIPANT" ],
"agreements": [ { "agreementId": "Catena-X", "consentStatus": "ACTIVE" } ]
}'
curl -s http://cxve.localhost/hub/api/members/<externalId> # follow the provisioningor let scripts/onboard-participant.sh do exactly that — submit through the hub and poll the
membership until it is PROVISIONED:
./scripts/onboard-participant.sh --name "My Company GmbH"All configuration is checked in statically in charts/cx-ve/values.yaml; a non-default
hostname (--host) is applied through a small, documented set of --set overrides in the
script.
The cluster is left running when the script finishes; remove it with
kind delete cluster -n cxve.
The whole sequence — install, onboarding of the "Verification Participant", and an external
resolution of its DID document (plain HTTP from the host through the gateway, exactly the way
an external dataspace solution resolves it) — runs as one end-to-end test (--skip-install
reuses an existing cluster):
./scripts/e2e.shThe first iteration of this repo demonstrated two federated VEs (cross-cluster routing, peer DNS, mutual issuer trust, cross-VE DSP exchange via
connect-ves.sh/dsp-tests.sh). That machinery was removed when the repo pivoted to a single Verification Environment that external solutions connect to; see git history and docs/cross-ve-communication.md (kept as a reference for the request flows that cross the VE boundary — the same flows an external solution performs). docs/sut-verification.md builds on it to define the state obligations and request ping-pong for verifying a third-party solution.
Apache License 2.0 — see LICENSE.