The Contract Management Service uses events defined by the TM Forum API to reflect the state of a contract in a data space. Depending on the provided data, permission to grant specified VerifiableCredentials is set at a Trusted Issuers List API service implementation.
The Contract Management Service is provided as a container at quay.io.
The container can be started with
docker run --network host quay.io/fiware/contract-management:0.0.1Configurations can be provided with the standard mechanisms of the Micronaut-Framework, e.g. environment variables or appliction.yaml file. The following table concentrates on the most important configuration parameters:
| Property | Env-Var | Description | Default |
|---|---|---|---|
micronaut.server.port |
MICRONAUT_SERVER_PORT |
Server port to be used for the listener endpoint. | 8080 |
micronaut.metrics.enabled |
MICRONAUT_METRICS_ENABLED |
Enable the metrics gathering | true |
micronaut.http.services.til.url |
MICRONAUT_HTTP_SERVICES_TIL_URL |
URL of the Trusted Issuers List Endpoint | http://trusted-issuers-list:8080 |
micronaut.http.services.til.path |
MICRONAUT_HTTP_SERVICES_TIL_PATH |
Subpath of the Trusted Issuers List Endpoint | "" |
micronaut.http.services.product.url |
MICRONAUT_HTTP_SERVICES_PRODUCT_URL |
URL of the TM Forum Product Order Endpoint | http://tmforum:8080 |
micronaut.http.services.product.path |
MICRONAUT_HTTP_SERVICES_PRODUCT_PATH |
Subpath of the TM Forum Product Order Endpoint | "productordering" |
micronaut.http.services.party.url |
MICRONAUT_HTTP_SERVICES_PARTY_URL |
URL of the TM Forum Party Endpoint | http://tmforum:8080 |
micronaut.http.services.party.path |
MICRONAUT_HTTP_SERVICES_PARTY_PATH |
Subpath of the TM Forum Party Endpoint | "party" |
general.name |
GENERAL_NAME |
Name of the service, used for the callback/listener subscription | contract-management |
general.basepath |
GENERAL_BASEPATH |
Basepath used for the provided listener endpoint | "" |
dataspace.events.dataspace-id |
DATASPACE_EVENTS_DATASPACE_ID |
Id of the data space; events carrying a different dataspaceid are rejected |
- |
dataspace.events.own-participant-id |
DATASPACE_EVENTS_OWN_PARTICIPANT_ID |
Participant id of this instance, events about it are not dispatched | general.did |
dataspace.events.listener.enabled |
DATASPACE_EVENTS_LISTENER_ENABLED |
Enable the data space event endpoint (startup only, needs a restart) | false |
dataspace.events.listener.identity.header |
DATASPACE_EVENTS_LISTENER_IDENTITY_HEADER |
Request header the API gateway sets with the claims of the token it verified | X-Userinfo |
dataspace.events.listener.identity.participant-claim |
DATASPACE_EVENTS_LISTENER_IDENTITY_PARTICIPANT_CLAIM |
Top-level claim carrying the caller's participant id | participantId |
dataspace.events.listener.dedup.max-entries |
DATASPACE_EVENTS_LISTENER_DEDUP_MAX_ENTRIES |
Number of remembered (source, id) pairs |
10000 |
dataspace.events.listener.dedup.ttl |
DATASPACE_EVENTS_LISTENER_DEDUP_TTL |
How long a (source, id) pair is remembered |
PT1H |
dataspace.events.listener.watch.enabled |
DATASPACE_EVENTS_LISTENER_WATCH_ENABLED |
Refresh when the config directory changes (startup only, needs a restart) | false |
dataspace.events.listener.watch.path |
DATASPACE_EVENTS_LISTENER_WATCH_PATH |
Watched configuration directory (e.g. the ConfigMap mount /config, not the file) |
- |
dataspace.events.publishers |
- | List of id (participant id), origin (webhook origin) and types (allowed event types); for the subscriber also subscriptions-url and optional client-id/scopes, see Subscribing to publishers |
[] |
dataspace.events.publisher.enabled |
DATASPACE_EVENTS_PUBLISHER_ENABLED |
Enable subscriptions and event delivery (startup only, needs a restart) | false |
dataspace.events.publisher.origin |
DATASPACE_EVENTS_PUBLISHER_ORIGIN |
Own DNS name, sent as WebHook-Request-Origin; required with the publisher |
- |
dataspace.events.publisher.lease-ttl |
DATASPACE_EVENTS_PUBLISHER_LEASE_TTL |
Lease of a subscription; it expires unless renewed within this time | PT24H |
dataspace.events.publisher.max-subscriptions-per-subscriber |
DATASPACE_EVENTS_PUBLISHER_MAX_SUBSCRIPTIONS_PER_SUBSCRIBER |
Subscriptions a single subscriber may hold | 10 |
dataspace.events.publisher.max-subscriptions |
DATASPACE_EVENTS_PUBLISHER_MAX_SUBSCRIPTIONS |
Subscriptions in total | 1000 |
dataspace.events.publisher.allow-insecure-sinks |
DATASPACE_EVENTS_PUBLISHER_ALLOW_INSECURE_SINKS |
Accept http sinks as well; otherwise only https |
false |
dataspace.events.publisher.sink-connect-timeout |
DATASPACE_EVENTS_PUBLISHER_SINK_CONNECT_TIMEOUT |
Connect timeout of handshake and delivery requests (startup only) | PT5S |
dataspace.events.publisher.sink-read-timeout |
DATASPACE_EVENTS_PUBLISHER_SINK_READ_TIMEOUT |
Read timeout of handshake and delivery requests (startup only) | PT10S |
dataspace.events.publisher.handshake-timeout |
DATASPACE_EVENTS_PUBLISHER_HANDSHAKE_TIMEOUT |
Upper bound of a whole handshake | PT15S |
dataspace.events.publisher.delivery.max-attempts |
DATASPACE_EVENTS_PUBLISHER_DELIVERY_MAX_ATTEMPTS |
Attempts per event, including the first one | 5 |
dataspace.events.publisher.delivery.initial-backoff |
DATASPACE_EVENTS_PUBLISHER_DELIVERY_INITIAL_BACKOFF |
Wait before the first retry, doubled on each further one | PT1S |
dataspace.events.publisher.delivery.max-backoff |
DATASPACE_EVENTS_PUBLISHER_DELIVERY_MAX_BACKOFF |
Upper bound of the wait between retries | PT1M |
dataspace.events.publisher.delivery.queue-size |
DATASPACE_EVENTS_PUBLISHER_DELIVERY_QUEUE_SIZE |
Pending events per subscription; further events are dropped while it is full | 1000 |
dataspace.events.publisher.delivery.attempt-timeout |
DATASPACE_EVENTS_PUBLISHER_DELIVERY_ATTEMPT_TIMEOUT |
Upper bound of one attempt incl. an OID4VP token fetch, not counting the wait for the rate limit | PT30S |
dataspace.events.subscriber.enabled |
DATASPACE_EVENTS_SUBSCRIBER_ENABLED |
Register the own listener at every publisher with a subscriptions-url (startup only, needs a restart and the listener) |
false |
dataspace.events.subscriber.sink |
DATASPACE_EVENTS_SUBSCRIBER_SINK |
Public absolute http(s) URL of the own POST /events; required with the subscriber |
- |
dataspace.events.subscriber.client-id |
DATASPACE_EVENTS_SUBSCRIBER_CLIENT_ID |
OID4VP client id towards the publishers' subscriptions API, unless a publisher overrides it | - |
dataspace.events.subscriber.scopes |
- | OID4VP scopes towards the publishers' subscriptions API, unless a publisher overrides them | [] |
dataspace.events.subscriber.protocol-settings.client-id |
DATASPACE_EVENTS_SUBSCRIBER_PROTOCOL_SETTINGS_CLIENT_ID |
Client at the own verifier the publishers use when the sink answers 401; required with the subscriber |
- |
dataspace.events.subscriber.protocol-settings.scopes |
- | Scopes at the own verifier, sent along with protocol-settings.client-id |
[] |
dataspace.events.subscriber.initial-delay |
DATASPACE_EVENTS_SUBSCRIBER_INITIAL_DELAY |
Wait after startup before the first registrations | PT5S |
dataspace.events.subscriber.retry.initial-backoff |
DATASPACE_EVENTS_SUBSCRIBER_RETRY_INITIAL_BACKOFF |
Wait before retrying a failed registration, doubled on each further failure | PT5S |
dataspace.events.subscriber.retry.max-backoff |
DATASPACE_EVENTS_SUBSCRIBER_RETRY_MAX_BACKOFF |
Upper bound of the wait between retries; retried without an attempt limit | PT5M |
dataspace.events.subscriber.renewal-fraction |
DATASPACE_EVENTS_SUBSCRIBER_RENEWAL_FRACTION |
Renew after this fraction of the remaining lease; values outside (0, 1) use the default |
0.5 |
dataspace.events.subscriber.min-renewal-delay |
DATASPACE_EVENTS_SUBSCRIBER_MIN_RENEWAL_DELAY |
Lower bound of the wait before a renewal | PT10S |
dataspace.events.subscriber.conflict-renewal-interval |
DATASPACE_EVENTS_SUBSCRIBER_CONFLICT_RENEWAL_INTERVAL |
Wait before re-registering after a 409 (or a 201 without expiresAt) |
PT1H |
dataspace.events.subscriber.reconcile-interval |
DATASPACE_EVENTS_SUBSCRIBER_RECONCILE_INTERVAL |
Interval of the periodic comparison of the configured publishers with the registrations | PT1M |
All dataspace.events.* values except the four enabled flags and the two sink-*-timeouts are reloaded on a refresh without
a restart. A key removed from
the configuration keeps its previous value until restart; set it to an empty value instead.
| Env-Var | Description | Default |
|---|---|---|
LOG_FORMAT |
TEXT for human readable lines, JSON for one JSON object per line (case-sensitive; an unknown value makes logback print why there is no output) |
TEXT |
LOG_LEVEL |
Root log level | INFO |
LOGGER_LEVELS_ORG_FIWARE_IAM |
Level of the contract-management itself, e.g. DEBUG to trace how an order is resolved |
INFO |
HTTP_CLIENT_LOG_EXCEPTION |
Add the stack trace to failed outgoing calls | false |
LOG_LEVEL_MICRONAUT_HTTP_CLIENT |
Level of micronaut's own http client logging (its connection errors duplicate the one-line WARN of the failed call) | OFF |
Levels are used as follows:
ERROR- the service itself is broken (e.g. an unexpected exception, a failed startup registration)WARN- something was not applied, always with the reason: a downstream service rejected a call (with its status and body), a configuration is invalid, an order handler failedINFO- business results: a notification was received, and one line per handled orderDEBUG- how the result was reached: every outgoing call, the resolved specifications, policies and credentials, each policy created at the PAP and each issuer allowed at the TIL
Every line concerning an order starts with Order <id>:. A failing order handler is logged once, with its name and the reason, and each handled order ends with one line, e.g.
WARN ProductOrderEventHandler - Order urn:ngsi-ld:product-order:1: completion failed in handler PapProductOrderHandler: [pap_rejected_policy] The PAP rejected policy p-1-urn:ngsi-ld:product-order:1 for assignee did:web:consumer.org. - downstream answered with status=400 body={"detail":"..."}
org.fiware.iam.exception.PapException: [pap_rejected_policy] The PAP rejected policy p-1-urn:ngsi-ld:product-order:1 for assignee did:web:consumer.org.
at ...
WARN ProductOrderEventHandler - Order urn:ngsi-ld:product-order:1: completion failed for customer urn:ngsi-ld:organization:2 in the handlers [PapProductOrderHandler].
Failures carry a machine-readable code in brackets ([organization_did_missing], [specification_not_resolvable], [til_rejected_issuer], ...), see org.fiware.iam.exception.FailureReason.
With dataspace.events.listener.enabled, CM receives CloudEvents (structured mode,
application/cloudevents+json) from other participants of the data space on POST /events (below general.basepath).
OPTIONS /events answers the CloudEvents webhook validation and grants a WebHook-Request-Origin only if it is the origin of a
configured publisher. Event types are dispatched to DataSpaceEventHandler beans; this release ships none.
A POST is checked in this order:
- The request must carry the identity header the gateway sets (
identity.header, by defaultX-Userinfo), else401. - Its claims must contain the participant id claim (
identity.participant-claim), else401. - The event
sourcemust equal that participant id, else403. - If both the event and
dataspace-idname a data space, they must match, else403. - A type no handler supports is answered with
202and ignored. - The
(source, type)pair must be configured indataspace.events.publishers, else403. - A
(source, id)seen before is answered with202and not dispatched again; while the first delivery is still being processed, the retry gets503withRetry-After. - An event whose
subjectis this participant (own-participant-id) is answered with202and not dispatched. - Otherwise the event is dispatched and answered with
202.
The counter dataspace.events.received (tags type, outcome) counts every request; type is other for types without a handler.
CM does not verify the bearer token of an incoming request. Like every other ingress path of CM, POST /events and the
subscriptions API expect an API gateway in front that verified the token and passes its claims on in a request header. CM only
reads the participant id out of that header.
The header is identity.header, by default X-Userinfo, the header APISIX' openid-connect plugin sets. Its value is the
claims of the verified token, either base64-encoded (standard or URL alphabet, padded or not) or as plain JSON. The participant
id is the top-level claim named in identity.participant-claim, by default participantId. A request without the header, with
an undecodable one, or without a usable claim is answered with 401; nothing else about the token is looked at, so an expired
or wrongly-addressed token is accepted if the gateway let it through.
A matching route for APISIX, whose openid-connect plugin verifies the token against the verifier's JWKS and clears a
client-supplied X-Userinfo before setting its own:
plugins:
openid-connect:
discovery: https://verifier.provider.org/.well-known/openid-configuration
client_id: contract-management
bearer_only: true
use_jwks: true
set_userinfo_header: true
claim_validator:
issuer:
valid_issuers: [ "https://verifier.provider.org" ]
audience:
claim: aud
required: true
match_with_client_id: trueThis only holds as long as the gateway is the only way to reach CM. The header is not signed, so anyone who can open a connection to CM's HTTP port can claim to be any participant: publish events in their name, subscribe for them, or delete their subscriptions. Restrict the port to the gateway (NetworkPolicy, service mesh) and make sure the gateway strips the header from incoming requests. The same holds for the management port, see Publishing events.
With dataspace.events.publisher.enabled, other participants subscribe a webhook (sink) to event types, and CM delivers
matching events to it. Events are published through the internal POST :9090/events. publisher.origin (CM's own DNS name) is
required, and so is own-participant-id or general.did, which becomes the source of every published event.
Subscribers are identified by the gateway, like the callers of the listener; the dataspace.events.listener.identity.*
configuration is shared with the listener and also applies when only the publisher is enabled.
A subscription follows the CloudEvents Subscriptions API,
only the protocol HTTP is supported:
curl -X POST https://cm.provider.org/subscriptions \
-H "Authorization: Bearer ${TOKEN}" -H "Content-Type: application/json" \
-d '{
"subscriberId": "did:web:consumer.org",
"sink": "https://events.consumer.org/events",
"types": [ "org.fiware.dataspace.example" ],
"protocol": "HTTP",
"protocolsettings": { "clientId": "contract-management", "scopes": [ "default" ] }
}'{
"id": "0f6a3c1e-8a52-4a3b-9a59-3f0c1f3f3a59",
"subscriberId": "did:web:consumer.org",
"sink": "https://events.consumer.org/events",
"types": [ "org.fiware.dataspace.example" ],
"protocol": "HTTP",
"protocolsettings": { "clientId": "contract-management", "scopes": [ "default" ] },
"expiresAt": "2026-10-31T09:00:00Z"
}clientId and scopes are used for the OID4VP flow when the sink answers a delivery with 401 (only with oid4vp.enabled).
A POST is checked in this order:
- The identity header must carry a participant id, else
401. subscriberIdis required (400) and must equal that participant id, else403.sinkmust be an absolutehttpsURI (httptoo withallow-insecure-sinks),typesmust not be empty,protocolmust beHTTPandprotocolsettings.clientIdis required, else400with areason.- Without a configured
publisher.origin, the request gets503. - Neither
max-subscriptions-per-subscribernormax-subscriptionsmay be reached, else503. Expired leases are evicted first, and a renewal does not count twice. A reached cap is a temporary refusal the subscriber retries; CM never answers409, which a subscriber reads as "the subscription already exists". - The sink must pass the webhook handshake, else
400with the reasonhandshake failed. - Otherwise the subscription is activated and answered with
201.
DELETE /subscriptions/{id} removes a subscription of the caller (204); an unknown id or one owned by another participant
gets 404.
Handshake: CM sends OPTIONS <sink> with WebHook-Request-Origin: <publisher.origin>. The sink has to answer with a 2xx
and a WebHook-Allowed-Origin equal to that origin (case-insensitive) or *. An optional WebHook-Allowed-Rate (requests per
minute, a positive integer or *) limits the deliveries to the sink. The handshake is bounded by handshake-timeout, single
requests by sink-connect-timeout and sink-read-timeout. CM only ever contacts the sink given in the request; no host is
derived from the subscriber id.
Leases: every subscription expires after lease-ttl; expiresAt tells when. To renew, the subscriber repeats the POST
for the same sink: the subscription keeps its id and pending events, and gets a new lease and the new types and protocol
settings. The handshake runs again; if it fails, the request gets 400 and the existing subscription stays unchanged until its
old lease expires.
Delivery is best-effort. Each event is POSTed in structured mode (application/cloudevents+json) to every active
subscription of its type, through a queue per subscription, so a slow or failing sink does not delay others. A full queue
drops further events until the sink catches up.
- A
2xxis delivered. Redirects are never followed, so a3xxis a failure. - Transport errors, timeouts,
408,429and5xxare retried with exponential backoff (initial-backoff, doubled up tomax-backoff) for at mostmax-attemptsattempts in total; afterwards the event is dropped and logged. - With
oid4vp.enabled, a401makes CM run OID4VP against the sink host with the subscription'sclientIdandscopes, and retry once with the token. A401after that is not retried. Without OID4VP, a401drops the event like any other4xx. - A
410 Goneremoves the subscription. Other4xxdrop the event without a retry. - The rate granted in the handshake counts HTTP requests, not events: an OID4VP-protected sink sees two requests per event (CM caches no OID4VP token), so grant twice the intended event rate.
The counter dataspace.events.delivered (tags type, outcome = delivered, failed, gone, expired, overflow) counts
the final result of every event at every subscription.
Subscriptions are kept in memory on a single replica: run the publisher with one replica. A restart loses all subscriptions and their pending events, and subscribers recover by renewing.
Publishing is done on the management port, e.g. by the marketplace or operator scripts inside the cluster:
curl -X POST http://contract-management:9090/events -H "Content-Type: application/json" \
-d '{ "type": "org.fiware.dataspace.example", "subject": "did:web:consumer.org", "data": { "offer": "urn:offer:1" } }'type is required (400), subject, data and id are optional. CM fills specversion (1.0), id (a random UUID when
absent), source (own-participant-id), time, dataspaceid and datacontenttype (application/json), queues the event and
answers 202 with the event id and the number of matching subscriptions, e.g. {"id": "...", "subscriptions": 2}. Without
own-participant-id and general.did it answers 503. The endpoint is not authenticated: keep port 9090 cluster-internal and
never expose it through the ingress. CM refuses to start the publisher when endpoints.all.port is unset or equal to
micronaut.server.port, since POST /events would then be public and collide with the listener.
Security notes:
- The subscriber chooses
sink,clientIdandscopes. Withoid4vp.enabled, when the sink answers401, CM presents the credentials the sink host's OID4VP request asks for. The resulting token is bound to that host, which limits replay, but the credentials are disclosed to it. Sinks are not restricted to theorigins indataspace.events.publishers, so only hold credentials in CM you are willing to present to any authenticated participant's host. - Handshake and delivery never follow redirects, so a sink cannot redirect CM to an internal address. The OID4VP token
fetch a sink's
401triggers uses its own non-redirecting client as well, so a redirect cannot move it to another host either. It still goes to the OID4VP endpoints the sink host itself advertises, which is inherent to the flow: run CM where it cannot reach hosts you would not let a participant name assink. - Events are broadcast: every participant the gateway identifies may subscribe to every type and then receives every event
of that type, including events whose
subjectis another participant. There is no per-subscriber type allowlist and no filtering bysubject, unlike thepublishers[].typesof the listener. Only publish events throughPOST :9090/eventsthat every authenticated participant of the data space may see. - A failed handshake is answered only with
handshake failed; the sink's status or the connection error is logged, never returned, so the API cannot be used to probe hosts reachable from CM.
With dataspace.events.subscriber.enabled, CM registers its own POST /events as webhook sink at every configured publisher
that has a subscriptions-url, using the publisher's POST /subscriptions (the API described above). This happens shortly after
startup (initial-delay) and whenever a refresh adds or changes a publisher. Each lease is renewed before it expires.
Startup fails unless all of the following hold:
dataspace.events.listener.enabledistrue, because the publisher'sOPTIONShandshake is answered by the listener.subscriber.sinkis the public absolute http(s) URL of the ownPOST /events, as the publishers reach it.subscriber.protocol-settings.client-idis set.own-participant-idorgeneral.didis set. It is sent assubscriberIdand has to equal the participant id that the publisher's gateway reads out of CM's token.
A publishers[] entry is only subscribed to if it has a subscriptions-url. CM never derives that URL from the publisher id or
origin, and entries without one are listener-only. A subscribable entry also needs:
types: the event types CM subscribes to at that publisher.origin: it must equal the publisher's owndataspace.events.publisher.origin. The publisher sends it asWebHook-Request-Originin the handshake, and the listener only grants configured origins.
An entry with a subscriptions-url but no types or no origin is skipped with a WARN and is not contacted. A wrong but
non-empty origin cannot be detected locally; the registration then fails with 400 handshake failed and is retried.
Two pairs of client credentials are used:
subscriber.client-id/subscriber.scopesare what CM uses for OID4VP towards the publisher's subscriptions API, i.e. when the publisher answers401. A publisher entry can override them with its ownclient-id/scopes. A blankclient-idor an emptyscopeslist counts as "no override".subscriber.protocol-settings.client-id/.scopesare sent asprotocolsettingsin the subscription. They name the client at CM's own verifier that the publisher uses when CM's sink answers a delivery with401.
Retries and renewal:
- A failed registration (transport error, timeout, any non-
2xxother than409, e.g. the503of a publisher at its cap) is retried without an attempt limit, because a publisher may be down for a long time. The wait starts atretry.initial-backoffand doubles up toretry.max-backoff. Until such a registration succeeds the publisher counts asPENDINGand, oncevalidUntilhas passed, makes the health checkDOWN. - After a
201, the lease is renewed afterrenewal-fractionof the remaining time untilexpiresAt, but never sooner thanmin-renewal-delay. Arenewal-fractionoutside(0, 1)falls back to0.5. A renewal repeats thePOSTfor the same sink, so the publisher keeps the subscription id. - A
409counts as success: it is logged at INFO as "409 treated as existing subscription", and the publisher is registered again afterconflict-renewal-interval. It is kept for publishers that report an existing subscription that way; a CM publisher never answers409(it renews with201and refuses a reached cap with503). The stateCONFLICTin the health details keeps that case visible. - A
201withoutexpiresAtis also registered again afterconflict-renewal-interval.
Configuration changes at runtime are applied on every refresh and by a periodic check every reconcile-interval:
- An added publisher is registered right away, without a restart.
- A publisher whose
subscriptions-url,types,origin,client-idorscopeschanged is registered again immediately. So is every publisher whensink,protocol-settings, the globalclient-id/scopesor the own participant id changed.- A changed
types,originor credential keeps the subscription key(subscriberId, sink). The publisher renews the subscription in place, and the old lease stays valid until then. - A changed
sink, participant id orsubscriptions-urlmeans a different subscription. CM first sends a best-effortDELETE <old subscriptions-url>/<old id>with the old credentials, then the newPOST. This frees the slot in the publisher's cap. Until the new registration succeeds, the publisher counts asPENDING.
- A changed
- A removed publisher, or one whose
subscriptions-urlwas set to"", is no longer retried or renewed and leaves the health check. CM sends a best-effortDELETE <subscriptions-url>/<id>; a failure is only logged, because the lease expires anyway. - An entry that becomes invalid (no
types, noorigin) is dropped the same way and shows asSKIPPED. - Without a known subscription id (never registered, or answered
409), nothing can be deleted, and the old lease simply expires.
A key removed from the configuration keeps its previous value, see below. To make a publisher listener-only at runtime, set
subscriptions-url: "". To drop a per-publisher override, set client-id: "" or scopes: [].
Health: the indicator dataspace-subscriptions is UP when every subscribable publisher holds a registration that has not
expired; once the first reconcile has run, that includes the case that no publisher is subscribable. Otherwise it reports the
status DEGRADED:
- from startup until the first reconcile (
initial-delay), and after that until every subscribable publisher is registered; - while a renewal keeps failing past
expiresAt(a failed renewal before that staysUP, because the publisher still delivers); - after a
sink, participant id orsubscriptions-urlchange, until the new registration succeeds; - while the subscriber settings are invalid after a refresh (detail
subscriber-settings).
It never reports DOWN. The publisher's handshake reaches CM through its Service or ingress. A pod that fails readiness would
never receive it, so registration could never succeed, and a failing liveness probe would restart the pod in a loop. /health
and /health/readiness include the indicator and keep answering 200 with DEGRADED; /health/liveness does not include it.
A DOWN of another indicator still wins. SKIPPED publishers do not affect the status.
The details map each publisher id to its state (PENDING, REGISTERED, CONFLICT, EXPIRED or SKIPPED), expiresAt,
and for SKIPPED the reason. Error responses of the publishers are only logged. An anonymous /health only shows the overall
status. The default endpoints.health.details-visible is AUTHENTICATED; to see the per-publisher details on the
cluster-internal port 9090, set it to ANONYMOUS.
dataspace:
events:
own-participant-id: "did:web:consumer.org"
listener:
enabled: true
publishers:
- id: "did:web:provider.org"
# must equal the provider's dataspace.events.publisher.origin
origin: "cm.provider.org"
types: [ "org.fiware.dataspace.example" ]
subscriptions-url: "https://cm.provider.org/subscriptions"
# optional, overrides subscriber.client-id / subscriber.scopes for this publisher
client-id: "contract-management-provider"
scopes: [ "events" ]
subscriber:
enabled: true
sink: "https://cm.consumer.org/events"
client-id: "contract-management"
scopes: [ "default" ]
protocol-settings:
client-id: "contract-management"
scopes: [ "default" ]The CM of the trust anchor can announce participants that left the data space. With
dataspace.events.tir-watcher.enabled, it polls the Trusted Issuers Registry (TIR) configured as the HTTP service
trusted-issuers-registry (micronaut.http.services.trusted-issuers-registry.url) every interval, enumerates all listed
DIDs through GET /v4/issuers and compares them with the previous snapshot. A DID that is missing for confirmations
consecutive complete cycles is published to the subscribers as an event of type org.fiware.dataspace.participant.removed
with subject and data.participantId set to the DID. Additions are taken into the snapshot but never announced. The latency
of an announcement is therefore about confirmations × interval (by default 2 minutes).
Startup fails with a message naming the key unless publisher.enabled is true (the events go out through the publisher),
own-participant-id or general.did is set (it is the event source), and every tir-watcher.* value is in its range.
Key (dataspace.events.tir-watcher.) |
Default | Range | Meaning |
|---|---|---|---|
enabled |
false |
Startup-time condition, needs a restart. | |
initial-delay |
PT30S |
positive | Delay of the first cycle after startup. |
interval |
PT1M |
positive | Delay between the end of one cycle and the start of the next. |
page-size |
100 |
1..100 | page[size] of the first request. |
max-pages |
1000 |
>= 1 | Pages per enumeration; a registry that needs more is never enumerated completely. |
confirmations |
2 |
>= 1 | Consecutive complete cycles a DID must be missing before its removal is announced. |
max-removals-per-cycle |
5 |
>= 0 | Circuit breaker, see below. 0 holds back every removal for the operator. |
An out-of-range value fails startup. After a refresh it is clamped into its range instead; an interval or initial-delay
that is not positive or empty falls back to its default.
Safeguards. The watcher only ever acts on a registry it can trust:
- Complete enumeration only. A cycle counts only if every page answered
2xx, every non-empty page reports thetotalof the first page, no DID is blank, and the number of distinct DIDs equals thattotal. The pages are followed through thepage[after](andpage[size]) of each page'slinks.next; host and path of the link are ignored, so the registry may report a host CM cannot reach. The cursor must be an integer, as the TIR API specifies it (FIWARE trusted-issuers-list and 0- or 1-based registries work). A registry with string cursors never completes an enumeration and logs a WARN every cycle; nothing is ever announced from it. An incomplete cycle (outcomeincomplete) changes nothing and logs a WARN (tir_enumeration_failed); it neither confirms nor refutes a pending removal. - Confirmations. A removal is announced only after
confirmationscomplete cycles in a row. A DID that is listed again before that is forgotten. - Circuit breaker. If more than
max-removals-per-cycleDIDs are missing at once, nothing changes and nothing is announced (outcomebreaker, alerttir_mass_removal). The count covers all DIDs missing from the snapshot, including those still awaiting confirmation from earlier cycles, so a registry that loses entries a few at a time is caught as well. Example (cap 5, confirmations 2): 3 DIDs removed in one cycle and 3 more in the next are 6 missing, and the breaker trips. It trips again on every cycle until the registry is restored or the cap is raised. If the removals are legitimate, raisemax-removals-per-cyclethroughPOST :9090/refreshand keep it raised for at leastconfirmationscycles (≈confirmations × interval): a tripped cycle does not count as a confirmation, so lowering the cap earlier trips the breaker again and the removals stay pending. - Empty registry. A registry that turns empty (from at least one participant to none) is far likelier wiped or
misconfigured than really empty. Nothing changes and nothing is announced (outcome
empty, alerttir_empty) until a participant is listed again. Consequently the removal of the last participant is never announced; it would have no recipient anyway. A TIR that is empty from the start is not alerted on: the first cycle logs one WARN (The trust anchor registry is empty; nothing to watch yet.) and the watcher waits for participants. - Silent first cycle. The first complete cycle only takes the snapshot (outcome
initial). The state is kept in memory, so after a restart the first cycle is silent again, and removals that happened while CM was down are not announced.
Delivery. A confirmed removal is published once at least one subscription of a participant other than the removed one
is registered for org.fiware.dataspace.participant.removed (the removed participant's own lease may still be active). Until
then, e.g. right after a publisher restart or in a subscriber's lease gap, it is retried on every complete cycle. While
cycles end incomplete, empty or breaker, the retry waits, so an alert or an unreachable registry also delays already
confirmed removals. Once the event is queued for at least one such subscription, the watcher is done with it: a queue that
overflows or a delivery that exhausts its retries afterwards is not retried by the watcher, and subscribers that register
later do not get it.
Alerts (tir_empty, tir_mass_removal) are visible in three ways:
- an ERROR log line with the code and the counts when the alert is raised, DEBUG while it persists, and INFO when the next
completeorinitialcycle clears it; - the counter
dataspace.tir.cycleswith tagoutcome(initial,complete,incomplete,empty,breaker), counted every cycle; announced removals count indataspace.tir.removals; - the health indicator
dataspace-tir-watcher, which reportsDEGRADEDwithreasontir_emptyortir_mass_removal. It also reportsDEGRADEDwithreason: no_successful_cyclewhen no cycle endedcompleteorinitialfor3 × interval(counted from when the first cycle was due, so the initial delay never counts). Its details show the last outcome, the last successful cycle and the sizes of the snapshot, the pending and the undelivered removals. As for the subscriptions,DEGRADEDkeeps/healthand the liveness and readiness probes at200.
dataspace:
events:
own-participant-id: "did:web:trust-anchor.org"
publisher:
enabled: true
origin: "cm.trust-anchor.org"
tir-watcher:
enabled: true
interval: PT1M
confirmations: 2
max-removals-per-cycle: 5
micronaut:
http:
services:
trusted-issuers-registry:
url: http://trusted-issuers-list:8080A participant can act on the org.fiware.dataspace.participant.removed events of a trust anchor (see above). The handling is
active when listener.enabled is true and at least one entry of dataspace.events.publishers lists that type in types.
Each such publisher has an on-participant-removed policy. It is applied in two places: to the publisher's events (the
subject, or data.participantId when there is no subject, is the removed participant) and in a reconciliation sweep that
runs per publisher shortly after startup and then on a timer. Nothing is written to the trusted-issuers-list (TIL) unless a
writing mode is configured: the default report only reads and reports.
dataspace:
events:
dataspace-id: "my-dataspace"
listener:
enabled: true
publishers:
- id: "did:web:trust-anchor.org"
origin: "cm.trust-anchor.org"
types: [ "org.fiware.dataspace.participant.removed" ]
on-participant-removed:
mode: dataspace
keep: [ "did:web:partner.org" ]
overrides:
- id: "did:web:special.org"
mode: report
max-removals-per-run: 5
adopt-unattributed: false
max-adoptions-per-run: 50
registry-url: "https://tir.trust-anchor.org"
participant-removed:
page-size: 100
max-pages: 1000
reconcile:
initial-delay: PT1M
interval: PT15MKey (publishers[].on-participant-removed.) |
Default | Meaning |
|---|---|---|
mode |
report |
report, dataspace, granted or full, see below. Every mode but report writes and requires registry-url. |
keep |
[] |
Identifiers never touched, whichever publisher removes them. |
overrides |
[] |
{id, mode} pairs replacing mode for one identifier (a list, because DIDs cannot be property-map keys). |
max-removals-per-run |
5 |
Breaker of the sweep, >= 0, see below. |
adopt-unattributed |
false |
Adoption of unattributed entries, see below. |
max-adoptions-per-run |
50 |
Adoptions per sweep, >= 0; the rest follow in the next sweep. |
registry-url |
Base URL of the publisher's trusted issuers registry (GET /v4/issuers). Needed by the sweep, the stale-event check and adoption. |
dataspace.events.participant-removed.page-size (1..100) and max-pages (>= 1) bound the listings of the TIL and of the
registries; reconcile.initial-delay and reconcile.interval (positive) time the sweep. All of these keys are refreshable.
Where an entry comes from. CM tells the origin of what an issuer holds in the TIL by its scope:
- a membership grant is
scope=<publisher id>withscopeType=dataspace-membership; - an order grant is an untyped scope (
scope=<order id>), as CM writes it for a product order; - credentials without a scope are unattributed, e.g. entries an operator provisioned by hand.
Modes. What each mode does with the identifier X that publisher P announces as removed:
| what X holds | report |
dataspace |
granted |
full |
|---|---|---|---|---|
| P's membership grant | kept | revoked | revoked | issuer deleted |
| another publisher's membership grant | kept | kept | kept | issuer deleted |
| order grants | kept | kept | revoked² | issuer deleted |
| only unattributed credentials | kept | kept¹ | kept¹ | issuer deleted |
| unattributed credentials next to grants | kept | kept | kept | issuer deleted |
¹ Revoked instead when adoption is allowed (see below) and X comes from an event: the publisher's explicit announcement
attributes an entry holding nothing but unattributed credentials to its data space. Without adoption, unattributed entries
survive every mode but full. Unattributed credentials next to grants are never touched, because the TIL cannot tell
which of them belong to which grant.
² Only when X is attributed to P alone: it holds P's membership grant and no other publisher's. Order grants carry no data space, so they are attributed through the entry's memberships. If X also holds P2's membership, its order grants may stem from contracts in P2's data space and are kept; if X holds no membership at all, nothing ties them to P and they are kept and reported. A later removal by P2 (then the only membership) revokes them.
Keep and overrides. An identifier on keep is protected against every publisher, although keep is configured
under one publisher: P1's keep of X stops P2 in full from deleting X's issuer entry. To stop only P's removals of X from
writing while other publishers still act, use an override {id: X, mode: report} under P. The own participant id
(own-participant-id, falling back to general.did) is never touched either, by events, the sweep or adoption: a full
event about our own removal does not delete our own issuer entry. An event whose subject is the own id is answered 202
before it reaches the handling (see the checks above) and is not counted; only an event naming the own id in
data.participantId and the sweep count it, as mode=keep, action=kept. An identifier on keep that also has an override
gets a WARN at startup: keep wins.
Invalid policy values. A value that cannot be converted, e.g. mode: dataspce or a non-numeric max-removals-per-run,
drops the whole publisher entry, which then is not authorized for any event type. For an entry that carries
on-participant-removed or lists participant.removed in types, startup fails with a message naming
dataspace.events.publishers[<index>] (when entries repeating an id differ only in a value outside the policy, it names
each of them it cannot tell apart); for other entries it is only a WARN, so existing deployments keep starting. After a refresh the sweep logs the same
findings, once per configuration change (ERROR for a dropped removal publisher). on-participant-removed takes effect only
when the entry's types include participant.removed; otherwise it gets a WARN and is ignored. A publisher split over several
entries needs a single policy: repeated removal entries of one id with different policies fail startup (after a refresh, the
first entry's policy applies).
Unknown keys. The binding accepts a key whose camelCase form is the field name, i.e. kebab-case or camelCase
(registry-url, registryUrl), and silently ignores any other key, so a misspelt or miscased key such as mod,
max-removal-per-run, maxremovalsperrun, registryURL or registry_url leaves the field at its default (report, no
registry). A key under on-participant-removed that names no policy field fails startup for an entry listing
participant.removed in types, naming dataspace.events.publishers[<index>].on-participant-removed.<key>; on other
entries it is a WARN. An unknown key directly on a publisher entry is a WARN only, so existing deployments keep starting,
except on an entry listing participant.removed when the key holds policy fields (e.g. on-participant-remove: {keep: [...]}):
the whole policy would be lost, a keep list included, so another publisher in full could delete an identifier the
operator meant to protect. That fails startup too. Such a key counts as a misspelt policy when it names the participant
(e.g. participant-removed) or sets a policy-specific field (keep, overrides, registry-url, ...); an unrelated map
that only has a mode key stays a WARN.
Lists. types, keep and override ids are YAML lists. A comma-separated string is not split: types: "a, org.fiware.dataspace.participant.removed" binds as one type, so the entry is no removal publisher, and
keep: "did:a,did:b" protects neither identifier. Such a string fails startup for an entry listing participant.removed
(also inside the string), naming the key; on other entries it is a WARN.
After a refresh the sweep logs these findings like the ones above.
Startup also fails, naming the key, for an override without id or mode, a registry-url that is not an absolute
http(s) URL, a value out of its range, a writing mode or writing override without registry-url (without it there is no
sweep, so a failed or dropped removal event is never completed), and an adopt-unattributed whose only obstacle is a missing
registry-url. After a refresh, a writing mode without registry-url acts as report (ERROR once) and out-of-range values
are clamped.
Stale events. An event may arrive late, after X was re-admitted. Before any write, the event path therefore asks P's
registry (GET {registry-url}/v4/issuers/{did}) whether X is listed again. If it is, nothing is written (action=still_listed,
202); a 404 lets the cleanup proceed; any other answer is a 502, so the publisher retries. A registry that lags behind
the announcement also gives still_listed; for an entry that holds P's membership the next sweep completes the cleanup. An
unattributed-only entry is not completed by the sweep, see the adoption caveat below.
No empty issuer entries. An issuer that holds no credential after a dataspace or granted cleanup would still be listed
in the TIL's registry view, and a verifier would then accept credentials of that issuer typed only with base types such as
VerifiableCredential. So when a cleanup would leave the entry empty, its last write deletes the issuer instead. The entry is
read again just before; if anything else appeared meanwhile, only the scope is revoked. An entry found already empty in a
writing mode is deleted as well. A credential written between that read and the delete is lost (the TIL has no conditional
delete). The report then shows the mode's revoking action with held: [].
Convergent cleanup. The TIL has no transactions. All reads come first, so a failed read changes nothing. Writes follow
with P's membership grant last, so an interrupted cleanup still leaves P's membership in place and the next sweep (or a
retry of the publisher) finds the entry and completes it. Every write is idempotent. When a TIL call fails while an event is
handled, the listener answers 502 Bad Gateway (outcome downstream_failed) and forgets the event, so the publisher retries
it.
Bounded publisher retries; the sweep is the reliable path. A publisher retries a 502 only for a while: a CM publisher
makes delivery.max-attempts attempts (by default 5, about 15 s) and then drops the event. A TIL outage longer than that loses
the event, and the sweep completes the cleanup, which is why writing modes require registry-url. One loss remains: with
adoption allowed, an unattributed-only entry that P delisted before the sweep adopted it is removed only by an event; if that
event is dropped or answered still_listed, the entry stays until a later event names it or the operator removes it.
The sweep. Every reconcile.interval and per publisher with a registry-url, CM lists the issuers holding P's membership
grant in the TIL and the participants in P's registry. Those holding P's membership although P no longer lists them are
handled with P's policy. The sweep never removes unattributed entries: an unattributed-only entry P does not list cannot be
told apart from an operator-managed one; only an event, which names the identifier, removes it. A registry listing that is
incomplete changes nothing.
Breaker. If a sweep finds more write candidates (identifiers whose effective mode writes) than max-removals-per-run, it
applies none of them and reports all of them: ERROR and action=breaker once when it trips or the candidates change, INFO
when it clears, and the health stays DEGRADED in between. report, keep and own-id candidates never trip it. Applying the
first N per run was rejected: a wrongly truncated registry would empty the TIL a few entries at a time. Events are not capped;
the trust anchor's watcher has its own breaker. 0 holds back every write for the operator.
Adoption. With adopt-unattributed: true, the sweep attributes entries of P's listed participants that hold nothing
but unattributed credentials to P's data space: it copies them into a membership grant of P and then removes the unattributed
originals. Once adopted, such an entry is a normal sweep candidate when P delists it. Adoption is allowed only when
dataspace-id is set, exactly one publisher announces participant.removed, it has adopt-unattributed: true and a
registry-url; otherwise it is refused (WARN, action=adoption_refused) and nothing is adopted. It assumes that this
participant belongs to P's data space alone: a participant in several data spaces must not enable it. The event filter does
not detect a second data space, because events without dataspaceid pass. At most max-adoptions-per-run entries are
adopted per sweep. keep and the own id are never adopted. The entry is read again before the originals are removed and
skipped if they changed; a credential added between that read and the removal is still lost (accepted race).
Contract Management itself writes membership grants only through adoption. Other components (e.g. onboarding, or entries
provisioned by the data-space-connector chart) that grant a participant membership of a data space must write them as
scope=<publisher id>, exactly the publisher's id in dataspace.events.publishers (the source of its events), with
scopeType=dataspace-membership, one grant per data space:
curl -X PUT "http://trusted-issuers-list:8080/issuer/did:web:consumer.org/credential?scope=did:web:trust-anchor.org&scopeType=dataspace-membership" \
-H "Content-Type: application/json" \
-d '[ { "credentialsType": "MembershipCredential", "claims": [] } ]'An entry written with an untyped scope is treated as an order grant, one without a scope as unattributed: such entries are
only reported, never revoked in dataspace, and survive every mode but full.
Reporting. Every decision is visible in three ways:
- a log line
Participant <did> removed by <publisher> (mode <mode>, <trigger>): <action>; still held: ...; - the counter
dsc.dataspace.events.participant.removed(Prometheusdsc_dataspace_events_participant_removed_total) with the tagspublisher(a configured publisher id),mode(report,dataspace,granted,full, orkeepfor an identifier onkeepor the own id) andaction(reported,kept,not_held,still_listed,revoked_membership,revoked_granted,deleted_issuer,adopted,adoption_refused,failed,breaker). Events count every time; the sweep counts an identifier only when it is new for the publisher or its mode changed, so a standing drift is not added every interval. The gaugedsc.dataspace.events.participant.removed.held{publisher}(Prometheusdsc_dataspace_events_participant_removed_held) is that standing drift: identifiers still holding P's membership although P's registry no longer lists them; - the read-only management endpoint
GET :9090/participants-removed, per removal publisher its effectivemode, the sharedtilstate (supported,pending,unverified,too-old),adoption(allowed,refused,off),breaker(tripped,clear) and the latest outcome per participant, with what it still holds:
[ {
"publisher": "did:web:trust-anchor.org", "mode": "dataspace", "til": "supported", "adoption": "off", "breaker": "clear",
"participants": [ {
"id": "did:web:gone.org", "trigger": "event", "lastSeen": "2026-11-10T12:00:00Z", "mode": "dataspace",
"action": "revoked_membership", "applied": true, "attributable": true, "held": [], "unattributedCount": 0
} ]
} ]held, attributable and unattributedCount are null when the entry was not read (e.g. a too-old TIL in report).
attributable: true with held: [] means that nothing is held any more. The endpoint shows which issuers this participant
trusts under which scopes, so it is served on the cluster-internal management port only; disable it with
endpoints.participants-removed.enabled: false.
The health indicator dataspace-participant-removed reports DEGRADED while a required TIL capability is unverified or
too-old or a breaker is tripped. It never reports DOWN, so the liveness and readiness probes are not affected.
Required trusted-issuers-list version. The handling needs typed scopes (TIL-1, scopeType filter), the
GET /issuer/{did}/scopes endpoint (TIL-2) and, while adoption is allowed, unscoped addressing (TIL-3). The TIL has no version
endpoint, so CM probes each capability without side effects, each probe bounded by 5 s:
- too old for a configured writing mode, writing override or allowed adoption: startup fails, naming the capability, the configuration that needs it and the TIL ticket;
- too old with
reportonly: ERROR once, healthDEGRADED, events are reported without what the participant holds, and no sweep runs; - unreachable or not decided (
401,403,5xx, timeout): startup continues with a WARN, healthDEGRADED, events are answered502(also inreport, which reads what the participant holds) and sweeps are skipped until a re-probe (at most every 30 s) succeeds. Events dropped meanwhile are completed by the first sweep after that.
One case is not caught at startup: a TIL with TIL-1 but without TIL-2 that is empty at startup (a fresh installation)
offers no issuer to probe /scopes with. It counts as pending, which does not block, since an empty TIL holds nothing. When
the first issuer appears, the re-probe finds the TIL too old: ERROR once, health DEGRADED, writes blocked and events in
writing modes answered 502. After the TIL is upgraded and CM restarted or refreshed, the sweep completes the cleanup. The
same holds after a refresh that switches to a writing mode while the TIL is too old.
Upgrade impact. Deployments whose publishers already list participant.removed in types activate the handling with this
release, in report mode: it only reads, degrades instead of failing on a too-old TIL, and turns the health DEGRADED, never
DOWN. Startup fails only for new configuration (a writing mode, an override or adoption that the TIL is too old for, or that
lacks registry-url), with two exceptions:
- Such a deployment that runs the management endpoints on the server port, or leaves
endpoints.all.portunset, now fails startup in the management-port check, also inreportmode, becauseGET /participants-removedwould be public. Set a separate cluster-internalendpoints.all.port, or disable the report withendpoints.participants-removed.enabled: false(or all management endpoints withendpoints.all.enabled: false). - Any deployment with the listener enabled now fails startup for an existing publisher entry that lists participant.removed in
types(or carrieson-participant-removed) but has a value that does not convert, e.g. a misspelt field value. Such an entry was already dropped before, so the publisher was silently unauthorized for all its event types; the error namesdataspace.events.publishers[<index>]. Fix the value. An entry without participant.removed that fails the same way only logs a WARN.
All dataspace.events.* values except listener.enabled, listener.watch.enabled, publisher.enabled, subscriber.enabled,
tir-watcher.enabled (startup-time conditions) and publisher.sink-connect-timeout/publisher.sink-read-timeout (used to build the sink client) are
refreshable; these need a restart. All other publisher.* values, e.g. the caps, the lease and the delivery settings, apply to
the next request or delivery attempt (delivery.queue-size only to subscriptions that receive their first event afterwards).
All other subscriber.* values and the publishers' subscriptions-url, client-id and scopes are refreshable as well; see
Subscribing to publishers for how the subscriber reacts.
All other tir-watcher.* values apply from the next cycle on, participant-removed.* and the on-participant-removed
policies from the next event or sweep.
A refresh is triggered by:
POST :9090/refreshreloads the configuration. The management port is not authenticated, keep port 9090 cluster-internal.- With
listener.watch.enabledandlistener.watch.path, CM watches the configuration directory and refreshes on any change in it.
A /refresh call and a watch-triggered refresh never run at the same time; the later one waits for the running one.
When the configuration comes from a Kubernetes ConfigMap, mount it as a directory (e.g. mountPath: /config with
MICRONAUT_CONFIG_FILES=/config/application.yaml and watch.path: /config), not with subPath: kubelet never updates
subPath mounts, so neither /refresh nor the watch would ever see a change. Kubelet propagates an update with a delay of up to
about a minute.
In order to support the development, a local environment can be started via mvn clean install -Pdev.
Contract Management supports events from different parts of the TMForum API.
In order to provide integration with the IDSA Protocols Catalog API, Catalog objects from TMForum are translated and pushed to Rainbow.
When receiving a "CatalogCreateEvent", it tries to translate a TMForum Catalog into an IDSA Catalog. The Catalog tilte dctColonTitle is taken from
the name property of the TMForum object.
When receiving a "CatalogStateChangeEvent" the changes from the TMForum Catalog Object are updated within the IDSA Catalog Object.
The Catalog with the same id as the contained Catalog-Object will be deleted.
In order to provide integration with the IDSA Protocols Catalog API, Product Offering objects from TMForum are translated to Data Services in Rainbow.
The "ProductOfferingCreateEvent" will be translated to Data Services and pushed to the Rainbow API. Only Offerings that are connected to an existing Catalog
will be pushed.
The offering requires a connected ProductSpecification, that contains productSpecCharacteristic if type:
endpointUrl: Will be used as "dcat:endpointURL" in the Data Service
endpointDescription: Will be used as "dcat:endpointDescription" in the Data Service
When receiving a "CatalogStateChangeEvent" the changes from the TMForum ProductOffering Object are updated within the IDSA Data Service Object.
The Data Service with the same id as the contained ProductOffering-Object will be deleted.
In order to support the IDSA Contract Negotiation the Contract Management integrates the TMForum Quote-API. See Dataspace Connector DSP Integration for more details.
The Product Order Object is used to integrate the TMForum with the authentication and authorization components of the Dataspace Connector.
A Product Order event will update the contract negotiation in TMForum when a Quote-Object is connected. Beside that, only Product Orders in state "completed" will be handled.
In case of a "completed" Product Order, the Product Specification linked in either the specification or the connected Quote will be taken and any Specification Characteristic
of type credentialsConfiguration will be inserted to the connected TrustedIssuers-List. Value can contain a list of Claim-Objects, as defined by the Trusted Issuers List API.
An example specification would look like:
{
"brand": "M&P Operations",
"version": "1.0.0",
"lifecycleStatus": "ACTIVE",
"name": "M&P K8S",
"productSpecCharacteristic": [
{
"id": "credentialsConfig",
"name": "Credentials Config",
"valueType": "credentialsConfiguration",
"productSpecCharacteristicValue": [
{
"isDefault": true,
"value": {
"credentialsType": "OperatorCredential",
"claims": [
{
"name": "roles",
"path": "$.roles[?(@.target==\\\"my-target-service\\\")].names[*]",
"allowedValues": [
"OPERATOR"
]
}
]
}
}
]
}
]
}Trusted-Issuers-List is licensed under the MIT License. See LICENSE for the full license text.
© 2023 FIWARE Foundation e.V.