Skip to content

feat(events): publish certificate-exchange status changes to NATS - #5

Merged
paullatzelsperger merged 3 commits into
Metaform:mainfrom
paullatzelsperger:feat/nats-exchange-events
Aug 13, 2026
Merged

feat(events): publish certificate-exchange status changes to NATS#5
paullatzelsperger merged 3 commits into
Metaform:mainfrom
paullatzelsperger:feat/nats-exchange-events

Conversation

@paullatzelsperger

@paullatzelsperger paullatzelsperger commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Certo held CX-0135 exchange state and emitted nothing — observing an exchange reaching FULFILLED or REJECTED meant polling the management API. It now publishes those status changes as CloudEvents onto the platform's shared edc-events JetStream stream, in the same envelope the EDC runtimes use, so existing consumers of that stream see them alongside the connector's.

  • Subjects events.certificate.exchange.<status>, types org.catena-x.ccm.CertificateExchange<Status>.v1 — both from one catalogue enum, with a test asserting it covers every FulfillmentStatus and AcceptanceStatus.
  • Emission via Spring Data @DomainEvents, not the ~13 service call sites: every mutation already goes through the aggregates' transition methods and is followed by save(), so no transition can be missed by forgetting a publish call.
  • AFTER_COMMIT delivery, so a rolled-back transition announces nothing. fallbackExecution = true is required, not defensive — five exchange operations are @Transactional(NOT_SUPPORTED) and their events would otherwise be discarded.
  • Off by default (certo.events.nats.enabled), so local runs and tests need no broker; the chart turns it on and reuses the platform's shared edc-events NATS identity (feat(nats): collapse NATS users into three shared identities eclipse-cfm/core-platform-distribution#19).

paullatzelsperger and others added 2 commits August 12, 2026 17:53
Certo held CX-0135 exchange state and emitted nothing, so the only way to
observe an exchange reaching FULFILLED or REJECTED was to poll its
management API. It now publishes those status changes as CloudEvents onto
the platform's shared edc-events JetStream stream, in the same envelope
shape and subject space the EDC runtimes use, so existing consumers of
that stream see certo's events alongside the connector's.

Subjects are events.certificate.exchange.<status>; types follow the
CX-0000 §2.3 reverse-DNS convention already used by CcmEvents. Both are
bound together in one catalogue enum (ExchangeEventType) so a consumer
routing on the subject and switching on the type can never see them
disagree, and a test asserts the mapping is total over FulfillmentStatus
and AcceptanceStatus -- a status that maps to nothing would publish
nothing, silently.

Emission hangs off Spring Data's @DomainEvents rather than the ~13
service call sites: every status mutation already funnels through the
aggregates' transition methods, and save() is already called after each
one, so recording there means a transition cannot be missed by forgetting
a publish call. The domain records facts; it does not publish them.

Two things that would each have silently lost events:

  - fallbackExecution = true on the @TransactionalEventListener is
    required, not defensive. pollAcceptance/publish (provider) and
    initiateRequest/pollRequest/retrieve (consumer) are declared
    @transactional(NOT_SUPPORTED) and commit through an inner
    TransactionTemplate, so by delivery time there is no surrounding
    transaction and Spring would discard those events -- losing the
    provider-initiated publish path entirely.
  - The consumer's updateFulfillment mirrors whatever the provider last
    reported and is called on every poll, so it records an event only
    when the status actually changes.

AFTER_COMMIT means a rolled-back transition announces nothing. Publish
failures are logged and swallowed, matching the EDC bridge: the state
change has already committed, and failing the API call would misreport
work that was done.

The CloudEvents `source` is the emitting application's hostname (the pod
name under Kubernetes), as in every other platform producer; the emitting
tenant travels in the CX-0000 §2.1.2 sourcebpn extension and in the
payload's participantContextId.

Off by default at the application level (certo.events.nats.enabled), so
local runs and the test suite need no broker; the chart turns it on. The
chart reuses the platform's shared `edc-events` NATS identity rather than
requiring a user of its own, which depends on core-platform-distribution
provisioning it and on certo's ServiceAccount being listed there.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@paullatzelsperger
paullatzelsperger force-pushed the feat/nats-exchange-events branch from 0b1c6cd to 5c88778 Compare August 13, 2026 07:07
@paullatzelsperger
paullatzelsperger merged commit ec22475 into Metaform:main Aug 13, 2026
1 check passed
@paullatzelsperger
paullatzelsperger deleted the feat/nats-exchange-events branch August 13, 2026 07:11
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.

2 participants