@twin.org/dataspace-control-plane-service 0.0.3-next.17 → 0.0.3-next.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,8 @@
1
- # TWIN Dataspace Control Plane Service
1
+ # Dataspace Control Plane Service
2
2
 
3
- Dataspace Control Plane contract implementation and REST endpoint definitions, implementing the Eclipse Dataspace Protocol (DSP) specification.
3
+ This package implements agreement negotiation and transfer process lifecycle management for control plane operations. It coordinates protocol-compliant state transitions and persists transfer state that can be consumed by downstream data plane components.
4
+
5
+ Its behaviour follows the [Eclipse Dataspace Protocol](https://eclipse-dataspace-protocol-base.github.io/DataspaceProtocol/) and is designed for policy-aware transfer orchestration.
4
6
 
5
7
  ## Installation
6
8
 
@@ -8,104 +10,6 @@ Dataspace Control Plane contract implementation and REST endpoint definitions, i
8
10
  npm install @twin.org/dataspace-control-plane-service
9
11
  ```
10
12
 
11
- ## Overview
12
-
13
- The Control Plane manages DSP protocol operations including contract negotiation and transfer process management.
14
-
15
- ### Key Features
16
-
17
- - **Transfer Process Management**: Create, start, complete, suspend, and terminate transfers
18
- - **Contract Negotiation**: Negotiate agreements with providers via Policy Negotiation Point (PNP)
19
- - **Agreement Validation**: Validate agreements via Policy Administration Point (PAP)
20
- - **Dataset Validation**: Validate datasets via Federated Catalogue
21
- - **Entity Storage**: Persists `TransferProcessEntity` to shared storage (accessible by Data Plane)
22
- - **Token Generation**: Creates transfer tokens with configurable expiration
23
- - **State Machine**: Enforces valid state transitions per DSP specification
24
-
25
- ### Constructor Options
26
-
27
- - `loggingComponentType` (default: `logging`) - Logging component type
28
- - `identityComponentType` (default: `identity`) - Identity component for token signing
29
- - `identityAuthenticationComponentType` (default: `identity-authentication`) - Token validation component
30
- - `transferProcessEntityStorageType` (default: `transfer-process-entity`) - Entity storage for transfer processes
31
- - `policyAdministrationPointComponentType` (default: `policy-administration-point`) - Agreement lookup component
32
- - `policyNegotiationPointComponentType` (default: `policy-negotiation-point`) - Contract negotiation component
33
- - `federatedCatalogueComponentType` (default: `federated-catalogue`) - Dataset validation component
34
- - `trustComponentType` (default: `trust`) - Trust verification component
35
-
36
- ### DSP Protocols
37
-
38
- The Control Plane implements two separate DSP protocols, following the Eclipse Dataspace Protocol specification:
39
-
40
- #### 1. Contract Negotiation Protocol
41
-
42
- Negotiate agreements with providers before initiating transfers. Integrates with the Rights Management Policy Negotiation Point (PNP).
43
-
44
- **REST API Endpoints:**
45
-
46
- - `POST /control-plane/negotiations` - Initiate contract negotiation
47
- - `GET /control-plane/negotiations/:negotiationId` - Get negotiation status
48
-
49
- **Example Flow:**
50
-
51
- ```bash
52
- # Step 1: Initiate negotiation
53
- curl -X POST https://consumer.example.com/control-plane/negotiations \
54
- -H "Authorization: Bearer ${TOKEN}" \
55
- -H "Content-Type: application/json" \
56
- -d '{
57
- "offerId": "offer-from-catalog",
58
- "providerEndpoint": "https://provider.example.com/negotiations"
59
- }'
60
- # Response: { agreement: {...}, agreementId: "...", negotiationId: "..." }
61
-
62
- # Step 2: Check negotiation status (optional)
63
- curl -X GET https://consumer.example.com/control-plane/negotiations/${NEGOTIATION_ID} \
64
- -H "Authorization: Bearer ${TOKEN}"
65
- # Response: { negotiationId: "...", state: "FINALIZED", ... }
66
- ```
67
-
68
- **Service Methods:**
69
-
70
- - `negotiateAgreement()` - Initiate contract negotiation (returns agreement)
71
- - `getNegotiation()` - Retrieve negotiation state
72
-
73
- **Flow**: Catalog → Negotiation → Agreement → Transfer
74
-
75
- #### 2. Transfer Process Protocol
76
-
77
- Manage the lifecycle of data transfers using negotiated agreements.
78
-
79
- **REST API Endpoints:**
80
-
81
- - `POST /control-plane/transfers/request` - Request a transfer
82
- - `GET /control-plane/transfers/:pid` - Get transfer process state
83
- - `POST /control-plane/transfers/:pid/start` - Start a transfer
84
- - `POST /control-plane/transfers/:pid/complete` - Complete a transfer
85
- - `POST /control-plane/transfers/:pid/suspend` - Suspend a transfer
86
- - `POST /control-plane/transfers/:pid/terminate` - Terminate a transfer
87
-
88
- **Service Methods:**
89
-
90
- - `requestTransfer()` - Initiate a transfer request (requires existing agreement)
91
- - `getTransferProcess()` - Retrieve transfer process state
92
- - `startTransfer()` - Start an approved transfer
93
- - `completeTransfer()` - Mark transfer as complete
94
- - `suspendTransfer()` - Temporarily suspend a transfer
95
- - `terminateTransfer()` - Terminate a transfer
96
-
97
- ### Shared Storage Architecture
98
-
99
- The Control Plane writes `TransferProcessEntity` to entity storage, which is read by the Data Plane for `consumerPid` resolution:
100
-
101
- ```text
102
- Control Plane Data Plane
103
- │ │
104
- │ set(TransferProcessEntity) │ get(consumerPid)
105
- │ │
106
- └───── Entity Storage ──────────┘
107
- ```
108
-
109
13
  ## Examples
110
14
 
111
15
  Usage of the APIs is shown in the examples [docs/examples.md](docs/examples.md)
@@ -37,6 +37,7 @@ export function generateRestRoutesDataspaceControlPlane(baseRouteName, component
37
37
  tag: tagsDataspaceControlPlane[0].name,
38
38
  method: "POST",
39
39
  path: `${baseRouteName}/transfers/request`,
40
+ skipAuth: true,
40
41
  handler: async (httpRequestContext, request) => requestTransferHandler(httpRequestContext, componentName, request)
41
42
  });
42
43
  // GET /transfers/:pid - Get Transfer Process state (DSP)
@@ -46,6 +47,7 @@ export function generateRestRoutesDataspaceControlPlane(baseRouteName, component
46
47
  tag: tagsDataspaceControlPlane[0].name,
47
48
  method: "GET",
48
49
  path: `${baseRouteName}/transfers/:pid`,
50
+ skipAuth: true,
49
51
  handler: async (httpRequestContext, request) => getTransferProcessHandler(httpRequestContext, componentName, request)
50
52
  });
51
53
  // POST /transfers/:pid/start - Start Transfer Process (DSP)
@@ -55,6 +57,7 @@ export function generateRestRoutesDataspaceControlPlane(baseRouteName, component
55
57
  tag: tagsDataspaceControlPlane[0].name,
56
58
  method: "POST",
57
59
  path: `${baseRouteName}/transfers/:pid/start`,
60
+ skipAuth: true,
58
61
  handler: async (httpRequestContext, request) => startTransferHandler(httpRequestContext, componentName, request)
59
62
  });
60
63
  // POST /transfers/:pid/complete - Complete Transfer Process (DSP)
@@ -64,6 +67,7 @@ export function generateRestRoutesDataspaceControlPlane(baseRouteName, component
64
67
  tag: tagsDataspaceControlPlane[0].name,
65
68
  method: "POST",
66
69
  path: `${baseRouteName}/transfers/:pid/complete`,
70
+ skipAuth: true,
67
71
  handler: async (httpRequestContext, request) => completeTransferHandler(httpRequestContext, componentName, request)
68
72
  });
69
73
  // POST /transfers/:pid/suspend - Suspend Transfer Process (DSP)
@@ -73,6 +77,7 @@ export function generateRestRoutesDataspaceControlPlane(baseRouteName, component
73
77
  tag: tagsDataspaceControlPlane[0].name,
74
78
  method: "POST",
75
79
  path: `${baseRouteName}/transfers/:pid/suspend`,
80
+ skipAuth: true,
76
81
  handler: async (httpRequestContext, request) => suspendTransferHandler(httpRequestContext, componentName, request)
77
82
  });
78
83
  // POST /transfers/:pid/terminate - Terminate Transfer Process (DSP)
@@ -82,6 +87,7 @@ export function generateRestRoutesDataspaceControlPlane(baseRouteName, component
82
87
  tag: tagsDataspaceControlPlane[0].name,
83
88
  method: "POST",
84
89
  path: `${baseRouteName}/transfers/:pid/terminate`,
90
+ skipAuth: true,
85
91
  handler: async (httpRequestContext, request) => terminateTransferHandler(httpRequestContext, componentName, request)
86
92
  });
87
93
  return routes;
@@ -1 +1 @@
1
- {"version":3,"file":"dataspaceControlPlaneRoutes.js","sourceRoot":"","sources":["../../src/dataspaceControlPlaneRoutes.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2025 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type {\n\tIHostingComponent,\n\tIHttpRequestContext,\n\tIRestRoute,\n\tITag\n} from \"@twin.org/api-models\";\nimport { ComponentFactory, Guards } from \"@twin.org/core\";\nimport type {\n\tIDataspaceControlPlaneComponent,\n\tIRequestTransferRequest,\n\tIRequestTransferResponse,\n\tIGetTransferProcessRequest,\n\tIGetTransferProcessResponse,\n\tIStartTransferRequest,\n\tIStartTransferResponse,\n\tICompleteTransferRequest,\n\tICompleteTransferResponse,\n\tISuspendTransferRequest,\n\tISuspendTransferResponse,\n\tITerminateTransferRequest,\n\tITerminateTransferResponse\n} from \"@twin.org/dataspace-models\";\nimport { nameof } from \"@twin.org/nameof\";\nimport { HeaderHelper, HeaderTypes } from \"@twin.org/web\";\nimport { transformErrorToStatusCode } from \"./utils/transferErrorUtils.js\";\n\n/**\n * The source used when communicating about these routes.\n */\nconst ROUTES_SOURCE = \"dataspaceControlPlaneRoutes\";\n\n/**\n * The tags to associate with the DSP protocol routes.\n */\nexport const tagsDataspaceControlPlane: ITag[] = [\n\t{\n\t\tname: \"Transfer Process\",\n\t\tdescription:\n\t\t\t\"DSP Transfer Process Protocol endpoints for initiating and managing data transfers.\"\n\t}\n];\n\n/**\n * The REST routes for dataspace control plane (DSP Protocol only).\n * These routes implement the Eclipse Dataspace Protocol Transfer Process Protocol.\n *\n * Contract Negotiation is handled internally via PNP callbacks — no REST endpoints needed.\n * PNP registers its own inbound callback routes for negotiation messages from providers.\n *\n * @param baseRouteName Prefix to prepend to the paths.\n * @param componentName The name of the component to use in the routes stored in the ComponentFactory.\n * @returns The generated DSP protocol routes.\n */\nexport function generateRestRoutesDataspaceControlPlane(\n\tbaseRouteName: string,\n\tcomponentName: string\n): IRestRoute[] {\n\tconst routes: IRestRoute[] = [];\n\n\t// ============================================================================\n\t// TRANSFER PROCESS PROTOCOL (DSP)\n\t// ============================================================================\n\n\t// POST /transfers/request - Request Transfer Process (DSP)\n\troutes.push({\n\t\toperationId: \"requestTransfer\",\n\t\tsummary: \"Request Transfer Process (DSP)\",\n\t\ttag: tagsDataspaceControlPlane[0].name,\n\t\tmethod: \"POST\",\n\t\tpath: `${baseRouteName}/transfers/request`,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\trequestTransferHandler(httpRequestContext, componentName, request)\n\t});\n\n\t// GET /transfers/:pid - Get Transfer Process state (DSP)\n\troutes.push({\n\t\toperationId: \"getTransferProcess\",\n\t\tsummary: \"Get Transfer Process state (DSP)\",\n\t\ttag: tagsDataspaceControlPlane[0].name,\n\t\tmethod: \"GET\",\n\t\tpath: `${baseRouteName}/transfers/:pid`,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tgetTransferProcessHandler(httpRequestContext, componentName, request)\n\t});\n\n\t// POST /transfers/:pid/start - Start Transfer Process (DSP)\n\troutes.push({\n\t\toperationId: \"startTransfer\",\n\t\tsummary: \"Start Transfer Process (DSP)\",\n\t\ttag: tagsDataspaceControlPlane[0].name,\n\t\tmethod: \"POST\",\n\t\tpath: `${baseRouteName}/transfers/:pid/start`,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tstartTransferHandler(httpRequestContext, componentName, request)\n\t});\n\n\t// POST /transfers/:pid/complete - Complete Transfer Process (DSP)\n\troutes.push({\n\t\toperationId: \"completeTransfer\",\n\t\tsummary: \"Complete Transfer Process (DSP)\",\n\t\ttag: tagsDataspaceControlPlane[0].name,\n\t\tmethod: \"POST\",\n\t\tpath: `${baseRouteName}/transfers/:pid/complete`,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tcompleteTransferHandler(httpRequestContext, componentName, request)\n\t});\n\n\t// POST /transfers/:pid/suspend - Suspend Transfer Process (DSP)\n\troutes.push({\n\t\toperationId: \"suspendTransfer\",\n\t\tsummary: \"Suspend Transfer Process (DSP)\",\n\t\ttag: tagsDataspaceControlPlane[0].name,\n\t\tmethod: \"POST\",\n\t\tpath: `${baseRouteName}/transfers/:pid/suspend`,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tsuspendTransferHandler(httpRequestContext, componentName, request)\n\t});\n\n\t// POST /transfers/:pid/terminate - Terminate Transfer Process (DSP)\n\troutes.push({\n\t\toperationId: \"terminateTransfer\",\n\t\tsummary: \"Terminate Transfer Process (DSP)\",\n\t\ttag: tagsDataspaceControlPlane[0].name,\n\t\tmethod: \"POST\",\n\t\tpath: `${baseRouteName}/transfers/:pid/terminate`,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tterminateTransferHandler(httpRequestContext, componentName, request)\n\t});\n\n\treturn routes;\n}\n\n// ============================================================================\n// TRANSFER PROCESS PROTOCOL HANDLERS\n// ============================================================================\n\n/**\n * Request Transfer Process handler.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use.\n * @param request The API request containing headers and body.\n * @returns The response.\n */\nasync function requestTransferHandler(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: IRequestTransferRequest\n): Promise<IRequestTransferResponse> {\n\tGuards.object(ROUTES_SOURCE, nameof(request), request);\n\tGuards.object(ROUTES_SOURCE, nameof(request.body), request.body);\n\n\tconst trustPayload = HeaderHelper.extractBearer(request.headers[HeaderTypes.Authorization]);\n\n\tconst component = ComponentFactory.get<IDataspaceControlPlaneComponent>(componentName);\n\tconst result = await component.requestTransfer(request.body, trustPayload);\n\n\treturn {\n\t\tbody: result,\n\t\tstatusCode: transformErrorToStatusCode(result)\n\t};\n}\n\n/**\n * Get Transfer Process handler.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use.\n * @param request The API request containing headers and path parameters.\n * @returns The response.\n */\nasync function getTransferProcessHandler(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: IGetTransferProcessRequest\n): Promise<IGetTransferProcessResponse> {\n\tGuards.object(ROUTES_SOURCE, nameof(request), request);\n\tGuards.object(ROUTES_SOURCE, nameof(request.pathParams), request.pathParams);\n\tGuards.stringValue(ROUTES_SOURCE, nameof(request.pathParams.pid), request.pathParams.pid);\n\n\tconst trustPayload = HeaderHelper.extractBearer(request.headers[HeaderTypes.Authorization]);\n\n\tconst component = ComponentFactory.get<IDataspaceControlPlaneComponent>(componentName);\n\tconst result = await component.getTransferProcess(request.pathParams.pid, trustPayload);\n\n\treturn {\n\t\tbody: result,\n\t\tstatusCode: transformErrorToStatusCode(result)\n\t};\n}\n\n/**\n * Start Transfer Process handler.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use.\n * @param request The API request containing headers, path parameters, and body.\n * @returns The response.\n */\nasync function startTransferHandler(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: IStartTransferRequest\n): Promise<IStartTransferResponse> {\n\tGuards.object(ROUTES_SOURCE, nameof(request), request);\n\tGuards.object(ROUTES_SOURCE, nameof(request.body), request.body);\n\n\tconst trustPayload = HeaderHelper.extractBearer(request.headers[HeaderTypes.Authorization]);\n\n\tconst hostingComponent = ComponentFactory.get<IHostingComponent>(\n\t\thttpRequestContext.hostingComponentType ?? \"hosting\"\n\t);\n\n\tconst component = ComponentFactory.get<IDataspaceControlPlaneComponent>(componentName);\n\tconst result = await component.startTransfer(\n\t\trequest.body,\n\t\tawait hostingComponent.getPublicOrigin(httpRequestContext.serverRequest.url),\n\t\ttrustPayload\n\t);\n\n\treturn {\n\t\tbody: result,\n\t\tstatusCode: transformErrorToStatusCode(result)\n\t};\n}\n\n/**\n * Complete Transfer Process handler.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use.\n * @param request The API request containing headers, path parameters, and body.\n * @returns The response.\n */\nasync function completeTransferHandler(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: ICompleteTransferRequest\n): Promise<ICompleteTransferResponse> {\n\tGuards.object(ROUTES_SOURCE, nameof(request), request);\n\tGuards.object(ROUTES_SOURCE, nameof(request.body), request.body);\n\n\tconst trustPayload = HeaderHelper.extractBearer(request.headers[HeaderTypes.Authorization]);\n\n\tconst component = ComponentFactory.get<IDataspaceControlPlaneComponent>(componentName);\n\tconst result = await component.completeTransfer(request.body, trustPayload);\n\n\treturn {\n\t\tbody: result,\n\t\tstatusCode: transformErrorToStatusCode(result)\n\t};\n}\n\n/**\n * Suspend Transfer Process handler.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use.\n * @param request The API request containing headers, path parameters, and body.\n * @returns The response.\n */\nasync function suspendTransferHandler(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: ISuspendTransferRequest\n): Promise<ISuspendTransferResponse> {\n\tGuards.object(ROUTES_SOURCE, nameof(request), request);\n\tGuards.object(ROUTES_SOURCE, nameof(request.body), request.body);\n\n\tconst trustPayload = HeaderHelper.extractBearer(request.headers[HeaderTypes.Authorization]);\n\n\tconst component = ComponentFactory.get<IDataspaceControlPlaneComponent>(componentName);\n\tconst result = await component.suspendTransfer(request.body, trustPayload);\n\n\treturn {\n\t\tbody: result,\n\t\tstatusCode: transformErrorToStatusCode(result)\n\t};\n}\n\n/**\n * Terminate Transfer Process handler.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use.\n * @param request The API request containing headers, path parameters, and body.\n * @returns The response.\n */\nasync function terminateTransferHandler(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: ITerminateTransferRequest\n): Promise<ITerminateTransferResponse> {\n\tGuards.object(ROUTES_SOURCE, nameof(request), request);\n\tGuards.object(ROUTES_SOURCE, nameof(request.body), request.body);\n\n\tconst trustPayload = HeaderHelper.extractBearer(request.headers[HeaderTypes.Authorization]);\n\n\tconst component = ComponentFactory.get<IDataspaceControlPlaneComponent>(componentName);\n\tconst result = await component.terminateTransfer(request.body, trustPayload);\n\n\treturn {\n\t\tbody: result,\n\t\tstatusCode: transformErrorToStatusCode(result)\n\t};\n}\n"]}
1
+ {"version":3,"file":"dataspaceControlPlaneRoutes.js","sourceRoot":"","sources":["../../src/dataspaceControlPlaneRoutes.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAiB1D,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAE3E;;GAEG;AACH,MAAM,aAAa,GAAG,6BAA6B,CAAC;AAEpD;;GAEG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAW;IAChD;QACC,IAAI,EAAE,kBAAkB;QACxB,WAAW,EACV,qFAAqF;KACtF;CACD,CAAC;AAEF;;;;;;;;;;GAUG;AACH,MAAM,UAAU,uCAAuC,CACtD,aAAqB,EACrB,aAAqB;IAErB,MAAM,MAAM,GAAiB,EAAE,CAAC;IAEhC,+EAA+E;IAC/E,kCAAkC;IAClC,+EAA+E;IAE/E,2DAA2D;IAC3D,MAAM,CAAC,IAAI,CAAC;QACX,WAAW,EAAE,iBAAiB;QAC9B,OAAO,EAAE,gCAAgC;QACzC,GAAG,EAAE,yBAAyB,CAAC,CAAC,CAAC,CAAC,IAAI;QACtC,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,GAAG,aAAa,oBAAoB;QAC1C,QAAQ,EAAE,IAAI;QACd,OAAO,EAAE,KAAK,EAAE,kBAAkB,EAAE,OAAO,EAAE,EAAE,CAC9C,sBAAsB,CAAC,kBAAkB,EAAE,aAAa,EAAE,OAAO,CAAC;KACnE,CAAC,CAAC;IAEH,yDAAyD;IACzD,MAAM,CAAC,IAAI,CAAC;QACX,WAAW,EAAE,oBAAoB;QACjC,OAAO,EAAE,kCAAkC;QAC3C,GAAG,EAAE,yBAAyB,CAAC,CAAC,CAAC,CAAC,IAAI;QACtC,MAAM,EAAE,KAAK;QACb,IAAI,EAAE,GAAG,aAAa,iBAAiB;QACvC,QAAQ,EAAE,IAAI;QACd,OAAO,EAAE,KAAK,EAAE,kBAAkB,EAAE,OAAO,EAAE,EAAE,CAC9C,yBAAyB,CAAC,kBAAkB,EAAE,aAAa,EAAE,OAAO,CAAC;KACtE,CAAC,CAAC;IAEH,4DAA4D;IAC5D,MAAM,CAAC,IAAI,CAAC;QACX,WAAW,EAAE,eAAe;QAC5B,OAAO,EAAE,8BAA8B;QACvC,GAAG,EAAE,yBAAyB,CAAC,CAAC,CAAC,CAAC,IAAI;QACtC,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,GAAG,aAAa,uBAAuB;QAC7C,QAAQ,EAAE,IAAI;QACd,OAAO,EAAE,KAAK,EAAE,kBAAkB,EAAE,OAAO,EAAE,EAAE,CAC9C,oBAAoB,CAAC,kBAAkB,EAAE,aAAa,EAAE,OAAO,CAAC;KACjE,CAAC,CAAC;IAEH,kEAAkE;IAClE,MAAM,CAAC,IAAI,CAAC;QACX,WAAW,EAAE,kBAAkB;QAC/B,OAAO,EAAE,iCAAiC;QAC1C,GAAG,EAAE,yBAAyB,CAAC,CAAC,CAAC,CAAC,IAAI;QACtC,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,GAAG,aAAa,0BAA0B;QAChD,QAAQ,EAAE,IAAI;QACd,OAAO,EAAE,KAAK,EAAE,kBAAkB,EAAE,OAAO,EAAE,EAAE,CAC9C,uBAAuB,CAAC,kBAAkB,EAAE,aAAa,EAAE,OAAO,CAAC;KACpE,CAAC,CAAC;IAEH,gEAAgE;IAChE,MAAM,CAAC,IAAI,CAAC;QACX,WAAW,EAAE,iBAAiB;QAC9B,OAAO,EAAE,gCAAgC;QACzC,GAAG,EAAE,yBAAyB,CAAC,CAAC,CAAC,CAAC,IAAI;QACtC,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,GAAG,aAAa,yBAAyB;QAC/C,QAAQ,EAAE,IAAI;QACd,OAAO,EAAE,KAAK,EAAE,kBAAkB,EAAE,OAAO,EAAE,EAAE,CAC9C,sBAAsB,CAAC,kBAAkB,EAAE,aAAa,EAAE,OAAO,CAAC;KACnE,CAAC,CAAC;IAEH,oEAAoE;IACpE,MAAM,CAAC,IAAI,CAAC;QACX,WAAW,EAAE,mBAAmB;QAChC,OAAO,EAAE,kCAAkC;QAC3C,GAAG,EAAE,yBAAyB,CAAC,CAAC,CAAC,CAAC,IAAI;QACtC,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,GAAG,aAAa,2BAA2B;QACjD,QAAQ,EAAE,IAAI;QACd,OAAO,EAAE,KAAK,EAAE,kBAAkB,EAAE,OAAO,EAAE,EAAE,CAC9C,wBAAwB,CAAC,kBAAkB,EAAE,aAAa,EAAE,OAAO,CAAC;KACrE,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AACf,CAAC;AAED,+EAA+E;AAC/E,qCAAqC;AACrC,+EAA+E;AAE/E;;;;;;GAMG;AACH,KAAK,UAAU,sBAAsB,CACpC,kBAAuC,EACvC,aAAqB,EACrB,OAAgC;IAEhC,MAAM,CAAC,MAAM,CAAC,aAAa,aAAmB,OAAO,CAAC,CAAC;IACvD,MAAM,CAAC,MAAM,CAAC,aAAa,kBAAwB,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjE,MAAM,YAAY,GAAG,YAAY,CAAC,aAAa,CAAC,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC,CAAC;IAE5F,MAAM,SAAS,GAAG,gBAAgB,CAAC,GAAG,CAAkC,aAAa,CAAC,CAAC;IACvF,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,eAAe,CAAC,OAAO,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;IAE3E,OAAO;QACN,IAAI,EAAE,MAAM;QACZ,UAAU,EAAE,0BAA0B,CAAC,MAAM,CAAC;KAC9C,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,KAAK,UAAU,yBAAyB,CACvC,kBAAuC,EACvC,aAAqB,EACrB,OAAmC;IAEnC,MAAM,CAAC,MAAM,CAAC,aAAa,aAAmB,OAAO,CAAC,CAAC;IACvD,MAAM,CAAC,MAAM,CAAC,aAAa,wBAA8B,OAAO,CAAC,UAAU,CAAC,CAAC;IAC7E,MAAM,CAAC,WAAW,CAAC,aAAa,4BAAkC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IAE1F,MAAM,YAAY,GAAG,YAAY,CAAC,aAAa,CAAC,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC,CAAC;IAE5F,MAAM,SAAS,GAAG,gBAAgB,CAAC,GAAG,CAAkC,aAAa,CAAC,CAAC;IACvF,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,kBAAkB,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;IAExF,OAAO;QACN,IAAI,EAAE,MAAM;QACZ,UAAU,EAAE,0BAA0B,CAAC,MAAM,CAAC;KAC9C,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,KAAK,UAAU,oBAAoB,CAClC,kBAAuC,EACvC,aAAqB,EACrB,OAA8B;IAE9B,MAAM,CAAC,MAAM,CAAC,aAAa,aAAmB,OAAO,CAAC,CAAC;IACvD,MAAM,CAAC,MAAM,CAAC,aAAa,kBAAwB,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjE,MAAM,YAAY,GAAG,YAAY,CAAC,aAAa,CAAC,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC,CAAC;IAE5F,MAAM,gBAAgB,GAAG,gBAAgB,CAAC,GAAG,CAC5C,kBAAkB,CAAC,oBAAoB,IAAI,SAAS,CACpD,CAAC;IAEF,MAAM,SAAS,GAAG,gBAAgB,CAAC,GAAG,CAAkC,aAAa,CAAC,CAAC;IACvF,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,aAAa,CAC3C,OAAO,CAAC,IAAI,EACZ,MAAM,gBAAgB,CAAC,eAAe,CAAC,kBAAkB,CAAC,aAAa,CAAC,GAAG,CAAC,EAC5E,YAAY,CACZ,CAAC;IAEF,OAAO;QACN,IAAI,EAAE,MAAM;QACZ,UAAU,EAAE,0BAA0B,CAAC,MAAM,CAAC;KAC9C,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,KAAK,UAAU,uBAAuB,CACrC,kBAAuC,EACvC,aAAqB,EACrB,OAAiC;IAEjC,MAAM,CAAC,MAAM,CAAC,aAAa,aAAmB,OAAO,CAAC,CAAC;IACvD,MAAM,CAAC,MAAM,CAAC,aAAa,kBAAwB,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjE,MAAM,YAAY,GAAG,YAAY,CAAC,aAAa,CAAC,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC,CAAC;IAE5F,MAAM,SAAS,GAAG,gBAAgB,CAAC,GAAG,CAAkC,aAAa,CAAC,CAAC;IACvF,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,gBAAgB,CAAC,OAAO,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;IAE5E,OAAO;QACN,IAAI,EAAE,MAAM;QACZ,UAAU,EAAE,0BAA0B,CAAC,MAAM,CAAC;KAC9C,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,KAAK,UAAU,sBAAsB,CACpC,kBAAuC,EACvC,aAAqB,EACrB,OAAgC;IAEhC,MAAM,CAAC,MAAM,CAAC,aAAa,aAAmB,OAAO,CAAC,CAAC;IACvD,MAAM,CAAC,MAAM,CAAC,aAAa,kBAAwB,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjE,MAAM,YAAY,GAAG,YAAY,CAAC,aAAa,CAAC,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC,CAAC;IAE5F,MAAM,SAAS,GAAG,gBAAgB,CAAC,GAAG,CAAkC,aAAa,CAAC,CAAC;IACvF,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,eAAe,CAAC,OAAO,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;IAE3E,OAAO;QACN,IAAI,EAAE,MAAM;QACZ,UAAU,EAAE,0BAA0B,CAAC,MAAM,CAAC;KAC9C,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,KAAK,UAAU,wBAAwB,CACtC,kBAAuC,EACvC,aAAqB,EACrB,OAAkC;IAElC,MAAM,CAAC,MAAM,CAAC,aAAa,aAAmB,OAAO,CAAC,CAAC;IACvD,MAAM,CAAC,MAAM,CAAC,aAAa,kBAAwB,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjE,MAAM,YAAY,GAAG,YAAY,CAAC,aAAa,CAAC,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC,CAAC;IAE5F,MAAM,SAAS,GAAG,gBAAgB,CAAC,GAAG,CAAkC,aAAa,CAAC,CAAC;IACvF,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,iBAAiB,CAAC,OAAO,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;IAE7E,OAAO;QACN,IAAI,EAAE,MAAM;QACZ,UAAU,EAAE,0BAA0B,CAAC,MAAM,CAAC;KAC9C,CAAC;AACH,CAAC","sourcesContent":["// Copyright 2025 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type {\n\tIHostingComponent,\n\tIHttpRequestContext,\n\tIRestRoute,\n\tITag\n} from \"@twin.org/api-models\";\nimport { ComponentFactory, Guards } from \"@twin.org/core\";\nimport type {\n\tIDataspaceControlPlaneComponent,\n\tIRequestTransferRequest,\n\tIRequestTransferResponse,\n\tIGetTransferProcessRequest,\n\tIGetTransferProcessResponse,\n\tIStartTransferRequest,\n\tIStartTransferResponse,\n\tICompleteTransferRequest,\n\tICompleteTransferResponse,\n\tISuspendTransferRequest,\n\tISuspendTransferResponse,\n\tITerminateTransferRequest,\n\tITerminateTransferResponse\n} from \"@twin.org/dataspace-models\";\nimport { nameof } from \"@twin.org/nameof\";\nimport { HeaderHelper, HeaderTypes } from \"@twin.org/web\";\nimport { transformErrorToStatusCode } from \"./utils/transferErrorUtils.js\";\n\n/**\n * The source used when communicating about these routes.\n */\nconst ROUTES_SOURCE = \"dataspaceControlPlaneRoutes\";\n\n/**\n * The tags to associate with the DSP protocol routes.\n */\nexport const tagsDataspaceControlPlane: ITag[] = [\n\t{\n\t\tname: \"Transfer Process\",\n\t\tdescription:\n\t\t\t\"DSP Transfer Process Protocol endpoints for initiating and managing data transfers.\"\n\t}\n];\n\n/**\n * The REST routes for dataspace control plane (DSP Protocol only).\n * These routes implement the Eclipse Dataspace Protocol Transfer Process Protocol.\n *\n * Contract Negotiation is handled internally via PNP callbacks — no REST endpoints needed.\n * PNP registers its own inbound callback routes for negotiation messages from providers.\n *\n * @param baseRouteName Prefix to prepend to the paths.\n * @param componentName The name of the component to use in the routes stored in the ComponentFactory.\n * @returns The generated DSP protocol routes.\n */\nexport function generateRestRoutesDataspaceControlPlane(\n\tbaseRouteName: string,\n\tcomponentName: string\n): IRestRoute[] {\n\tconst routes: IRestRoute[] = [];\n\n\t// ============================================================================\n\t// TRANSFER PROCESS PROTOCOL (DSP)\n\t// ============================================================================\n\n\t// POST /transfers/request - Request Transfer Process (DSP)\n\troutes.push({\n\t\toperationId: \"requestTransfer\",\n\t\tsummary: \"Request Transfer Process (DSP)\",\n\t\ttag: tagsDataspaceControlPlane[0].name,\n\t\tmethod: \"POST\",\n\t\tpath: `${baseRouteName}/transfers/request`,\n\t\tskipAuth: true,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\trequestTransferHandler(httpRequestContext, componentName, request)\n\t});\n\n\t// GET /transfers/:pid - Get Transfer Process state (DSP)\n\troutes.push({\n\t\toperationId: \"getTransferProcess\",\n\t\tsummary: \"Get Transfer Process state (DSP)\",\n\t\ttag: tagsDataspaceControlPlane[0].name,\n\t\tmethod: \"GET\",\n\t\tpath: `${baseRouteName}/transfers/:pid`,\n\t\tskipAuth: true,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tgetTransferProcessHandler(httpRequestContext, componentName, request)\n\t});\n\n\t// POST /transfers/:pid/start - Start Transfer Process (DSP)\n\troutes.push({\n\t\toperationId: \"startTransfer\",\n\t\tsummary: \"Start Transfer Process (DSP)\",\n\t\ttag: tagsDataspaceControlPlane[0].name,\n\t\tmethod: \"POST\",\n\t\tpath: `${baseRouteName}/transfers/:pid/start`,\n\t\tskipAuth: true,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tstartTransferHandler(httpRequestContext, componentName, request)\n\t});\n\n\t// POST /transfers/:pid/complete - Complete Transfer Process (DSP)\n\troutes.push({\n\t\toperationId: \"completeTransfer\",\n\t\tsummary: \"Complete Transfer Process (DSP)\",\n\t\ttag: tagsDataspaceControlPlane[0].name,\n\t\tmethod: \"POST\",\n\t\tpath: `${baseRouteName}/transfers/:pid/complete`,\n\t\tskipAuth: true,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tcompleteTransferHandler(httpRequestContext, componentName, request)\n\t});\n\n\t// POST /transfers/:pid/suspend - Suspend Transfer Process (DSP)\n\troutes.push({\n\t\toperationId: \"suspendTransfer\",\n\t\tsummary: \"Suspend Transfer Process (DSP)\",\n\t\ttag: tagsDataspaceControlPlane[0].name,\n\t\tmethod: \"POST\",\n\t\tpath: `${baseRouteName}/transfers/:pid/suspend`,\n\t\tskipAuth: true,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tsuspendTransferHandler(httpRequestContext, componentName, request)\n\t});\n\n\t// POST /transfers/:pid/terminate - Terminate Transfer Process (DSP)\n\troutes.push({\n\t\toperationId: \"terminateTransfer\",\n\t\tsummary: \"Terminate Transfer Process (DSP)\",\n\t\ttag: tagsDataspaceControlPlane[0].name,\n\t\tmethod: \"POST\",\n\t\tpath: `${baseRouteName}/transfers/:pid/terminate`,\n\t\tskipAuth: true,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tterminateTransferHandler(httpRequestContext, componentName, request)\n\t});\n\n\treturn routes;\n}\n\n// ============================================================================\n// TRANSFER PROCESS PROTOCOL HANDLERS\n// ============================================================================\n\n/**\n * Request Transfer Process handler.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use.\n * @param request The API request containing headers and body.\n * @returns The response.\n */\nasync function requestTransferHandler(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: IRequestTransferRequest\n): Promise<IRequestTransferResponse> {\n\tGuards.object(ROUTES_SOURCE, nameof(request), request);\n\tGuards.object(ROUTES_SOURCE, nameof(request.body), request.body);\n\n\tconst trustPayload = HeaderHelper.extractBearer(request.headers[HeaderTypes.Authorization]);\n\n\tconst component = ComponentFactory.get<IDataspaceControlPlaneComponent>(componentName);\n\tconst result = await component.requestTransfer(request.body, trustPayload);\n\n\treturn {\n\t\tbody: result,\n\t\tstatusCode: transformErrorToStatusCode(result)\n\t};\n}\n\n/**\n * Get Transfer Process handler.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use.\n * @param request The API request containing headers and path parameters.\n * @returns The response.\n */\nasync function getTransferProcessHandler(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: IGetTransferProcessRequest\n): Promise<IGetTransferProcessResponse> {\n\tGuards.object(ROUTES_SOURCE, nameof(request), request);\n\tGuards.object(ROUTES_SOURCE, nameof(request.pathParams), request.pathParams);\n\tGuards.stringValue(ROUTES_SOURCE, nameof(request.pathParams.pid), request.pathParams.pid);\n\n\tconst trustPayload = HeaderHelper.extractBearer(request.headers[HeaderTypes.Authorization]);\n\n\tconst component = ComponentFactory.get<IDataspaceControlPlaneComponent>(componentName);\n\tconst result = await component.getTransferProcess(request.pathParams.pid, trustPayload);\n\n\treturn {\n\t\tbody: result,\n\t\tstatusCode: transformErrorToStatusCode(result)\n\t};\n}\n\n/**\n * Start Transfer Process handler.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use.\n * @param request The API request containing headers, path parameters, and body.\n * @returns The response.\n */\nasync function startTransferHandler(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: IStartTransferRequest\n): Promise<IStartTransferResponse> {\n\tGuards.object(ROUTES_SOURCE, nameof(request), request);\n\tGuards.object(ROUTES_SOURCE, nameof(request.body), request.body);\n\n\tconst trustPayload = HeaderHelper.extractBearer(request.headers[HeaderTypes.Authorization]);\n\n\tconst hostingComponent = ComponentFactory.get<IHostingComponent>(\n\t\thttpRequestContext.hostingComponentType ?? \"hosting\"\n\t);\n\n\tconst component = ComponentFactory.get<IDataspaceControlPlaneComponent>(componentName);\n\tconst result = await component.startTransfer(\n\t\trequest.body,\n\t\tawait hostingComponent.getPublicOrigin(httpRequestContext.serverRequest.url),\n\t\ttrustPayload\n\t);\n\n\treturn {\n\t\tbody: result,\n\t\tstatusCode: transformErrorToStatusCode(result)\n\t};\n}\n\n/**\n * Complete Transfer Process handler.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use.\n * @param request The API request containing headers, path parameters, and body.\n * @returns The response.\n */\nasync function completeTransferHandler(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: ICompleteTransferRequest\n): Promise<ICompleteTransferResponse> {\n\tGuards.object(ROUTES_SOURCE, nameof(request), request);\n\tGuards.object(ROUTES_SOURCE, nameof(request.body), request.body);\n\n\tconst trustPayload = HeaderHelper.extractBearer(request.headers[HeaderTypes.Authorization]);\n\n\tconst component = ComponentFactory.get<IDataspaceControlPlaneComponent>(componentName);\n\tconst result = await component.completeTransfer(request.body, trustPayload);\n\n\treturn {\n\t\tbody: result,\n\t\tstatusCode: transformErrorToStatusCode(result)\n\t};\n}\n\n/**\n * Suspend Transfer Process handler.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use.\n * @param request The API request containing headers, path parameters, and body.\n * @returns The response.\n */\nasync function suspendTransferHandler(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: ISuspendTransferRequest\n): Promise<ISuspendTransferResponse> {\n\tGuards.object(ROUTES_SOURCE, nameof(request), request);\n\tGuards.object(ROUTES_SOURCE, nameof(request.body), request.body);\n\n\tconst trustPayload = HeaderHelper.extractBearer(request.headers[HeaderTypes.Authorization]);\n\n\tconst component = ComponentFactory.get<IDataspaceControlPlaneComponent>(componentName);\n\tconst result = await component.suspendTransfer(request.body, trustPayload);\n\n\treturn {\n\t\tbody: result,\n\t\tstatusCode: transformErrorToStatusCode(result)\n\t};\n}\n\n/**\n * Terminate Transfer Process handler.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use.\n * @param request The API request containing headers, path parameters, and body.\n * @returns The response.\n */\nasync function terminateTransferHandler(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: ITerminateTransferRequest\n): Promise<ITerminateTransferResponse> {\n\tGuards.object(ROUTES_SOURCE, nameof(request), request);\n\tGuards.object(ROUTES_SOURCE, nameof(request.body), request.body);\n\n\tconst trustPayload = HeaderHelper.extractBearer(request.headers[HeaderTypes.Authorization]);\n\n\tconst component = ComponentFactory.get<IDataspaceControlPlaneComponent>(componentName);\n\tconst result = await component.terminateTransfer(request.body, trustPayload);\n\n\treturn {\n\t\tbody: result,\n\t\tstatusCode: transformErrorToStatusCode(result)\n\t};\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import { ContextIdKeys, ContextIdStore } from "@twin.org/context";
2
- import { ArrayHelper, BaseError, ComponentFactory, Converter, GeneralError, Guards, Is, NotFoundError, ObjectHelper, RandomHelper, StringHelper, UnauthorizedError } from "@twin.org/core";
2
+ import { ArrayHelper, BaseError, ComponentFactory, Converter, GeneralError, Guards, Is, NotFoundError, RandomHelper, StringHelper, UnauthorizedError, ValidationError } from "@twin.org/core";
3
3
  import { JsonLdHelper } from "@twin.org/data-json-ld";
4
4
  import { DataspaceAppFactory, TransferProcessRole } from "@twin.org/dataspace-models";
5
5
  import { EngineCoreFactory } from "@twin.org/engine-models";
@@ -333,10 +333,9 @@ export class DataspaceControlPlaneService {
333
333
  * Called by: Consumer when it wants to request a new Transfer Process
334
334
  */
335
335
  async requestTransfer(request, trustPayload) {
336
- await TrustHelper.verifyTrust(this._trustComponent, trustPayload, "requestTransfer");
337
- const validationFailures = [];
338
- const isConformant = await DataspaceProtocolHelper.checkConformance(JsonLdHelper.toNodeObject(request), validationFailures);
339
- if (!isConformant) {
336
+ const trustInfo = await TrustHelper.verifyTrust(this._trustComponent, trustPayload, "requestTransfer");
337
+ const validationFailures = await DataspaceProtocolHelper.validate(JsonLdHelper.toNodeObject(request));
338
+ if (validationFailures.length > 0) {
340
339
  await this._loggingComponent?.log({
341
340
  level: "error",
342
341
  source: DataspaceControlPlaneService.CLASS_NAME,
@@ -344,9 +343,7 @@ export class DataspaceControlPlaneService {
344
343
  message: "invalidTransferRequest",
345
344
  data: { validationFailures }
346
345
  });
347
- throw new GeneralError(DataspaceControlPlaneService.CLASS_NAME, "invalidTransferRequest", {
348
- validationFailures
349
- });
346
+ return transformToTransferError(new ValidationError(DataspaceControlPlaneService.CLASS_NAME, "request", validationFailures), request);
350
347
  }
351
348
  const providerPid = `urn:uuid:${RandomHelper.generateUuidV7()}`;
352
349
  let datasetId;
@@ -372,9 +369,17 @@ export class DataspaceControlPlaneService {
372
369
  });
373
370
  }
374
371
  const assigneeIdentity = OdrlPolicyHelper.extractAssigneeIdentity(agreement);
375
- consumerIdentity = Is.array(assigneeIdentity) ? assigneeIdentity[0] : assigneeIdentity;
372
+ const assigneeIds = ArrayHelper.fromObjectOrArray(assigneeIdentity);
373
+ if (!assigneeIds.includes(trustInfo.identity)) {
374
+ throw new UnauthorizedError(DataspaceControlPlaneService.CLASS_NAME, "callerNotAuthorizedForAgreement");
375
+ }
376
+ consumerIdentity = trustInfo.identity;
376
377
  const assignerIdentity = OdrlPolicyHelper.extractAssignerIdentity(agreement);
377
- providerIdentity = Is.array(assignerIdentity) ? assignerIdentity[0] : assignerIdentity;
378
+ const assignerIds = ArrayHelper.fromObjectOrArray(assignerIdentity);
379
+ if (assignerIds.length > 1) {
380
+ throw new GeneralError(DataspaceControlPlaneService.CLASS_NAME, "multipleAssignersNotSupported");
381
+ }
382
+ providerIdentity = assignerIds[0];
378
383
  datasetId = this.extractDatasetId(agreement);
379
384
  await this.validateCatalogDataset(datasetId, agreement);
380
385
  policies = [agreement];
@@ -390,8 +395,8 @@ export class DataspaceControlPlaneService {
390
395
  state: DataspaceProtocolTransferProcessStateType.REQUESTED,
391
396
  agreementId: request.agreementId,
392
397
  datasetId,
393
- consumerIdentity: ArrayHelper.fromObjectOrArray(consumerIdentity)[0],
394
- providerIdentity: ArrayHelper.fromObjectOrArray(providerIdentity)[0],
398
+ consumerIdentity,
399
+ providerIdentity,
395
400
  // offerId should reference Catalog Offer (via Agreement)
396
401
  // For now, use agreementId as reference (proper flow: Catalog → Negotiation → Agreement)
397
402
  offerId: request.agreementId,
@@ -437,16 +442,21 @@ export class DataspaceControlPlaneService {
437
442
  * Role Performed: Provider / Consumer
438
443
  */
439
444
  async startTransfer(message, publicOrigin, trustPayload) {
440
- await TrustHelper.verifyTrust(this._trustComponent, trustPayload, "startTransfer");
441
- const validationFailures = [];
442
- const isConformant = await DataspaceProtocolHelper.checkConformance(JsonLdHelper.toNodeObject(message), validationFailures);
443
- if (!isConformant) {
444
- return transformToTransferError(new GeneralError(DataspaceControlPlaneService.CLASS_NAME, "invalidTransferStartMessage", {
445
- validationFailures
446
- }), message);
445
+ const trustInfo = await TrustHelper.verifyTrust(this._trustComponent, trustPayload, "startTransfer");
446
+ const validationFailures = await DataspaceProtocolHelper.validate(JsonLdHelper.toNodeObject(message));
447
+ if (validationFailures.length > 0) {
448
+ await this._loggingComponent?.log({
449
+ level: "error",
450
+ source: DataspaceControlPlaneService.CLASS_NAME,
451
+ ts: Date.now(),
452
+ message: "invalidTransferStartMessage",
453
+ data: { validationFailures }
454
+ });
455
+ return transformToTransferError(new ValidationError(DataspaceControlPlaneService.CLASS_NAME, "message", validationFailures), message);
447
456
  }
448
457
  try {
449
458
  const { entity, role } = await this.lookupTransferByMessage(message);
459
+ this.validateCallerIsProvider(trustInfo.identity, entity);
450
460
  if (entity.state !== DataspaceProtocolTransferProcessStateType.REQUESTED &&
451
461
  entity.state !== DataspaceProtocolTransferProcessStateType.SUSPENDED) {
452
462
  return transformToTransferError(new GeneralError(DataspaceControlPlaneService.CLASS_NAME, "invalidStateForStart", {
@@ -501,13 +511,21 @@ export class DataspaceControlPlaneService {
501
511
  providerPid: entity.providerPid
502
512
  }), message);
503
513
  }
504
- const accessToken = await this._trustComponent.generate(entity.consumerIdentity ?? entity.consumerPid, this._overrideTrustGeneratorType, {
505
- consumerPid: entity.consumerPid,
506
- providerPid: entity.providerPid,
507
- agreementId: entity.agreementId,
508
- datasetId: entity.datasetId
514
+ // Provider signs the data access token with its own identity.
515
+ // The subject contains the transfer context claims that the data plane
516
+ // will verify when the consumer presents this token.
517
+ if (!Is.stringValue(entity.providerIdentity)) {
518
+ throw new GeneralError(DataspaceControlPlaneService.CLASS_NAME, "providerIdentityMissing");
519
+ }
520
+ const accessToken = await this._trustComponent.generate(entity.providerIdentity, this._overrideTrustGeneratorType, {
521
+ subject: {
522
+ consumerPid: entity.consumerPid,
523
+ providerPid: entity.providerPid,
524
+ agreementId: entity.agreementId,
525
+ datasetId: entity.datasetId
526
+ }
509
527
  });
510
- const tokenString = Converter.bytesToBase64(ObjectHelper.toBytes(accessToken));
528
+ const tokenString = accessToken;
511
529
  const fullEndpoint = `${publicOrigin}/${this._dataPlanePath}`;
512
530
  response.dataAddress = {
513
531
  "@type": DataspaceProtocolTransferProcessTypes.DataAddress,
@@ -586,14 +604,21 @@ export class DataspaceControlPlaneService {
586
604
  * @returns Transfer Process (DSP compliant) with state COMPLETED, or TransferError if the operation fails.
587
605
  */
588
606
  async completeTransfer(message, trustPayload) {
589
- await TrustHelper.verifyTrust(this._trustComponent, trustPayload, "completeTransfer");
590
- const validationFailures = [];
591
- const isConformant = await DataspaceProtocolHelper.checkConformance(JsonLdHelper.toNodeObject(message), validationFailures);
592
- if (!isConformant) {
593
- return transformToTransferError(new GeneralError(DataspaceControlPlaneService.CLASS_NAME, "invalidTransferCompletionMessage", { validationFailures }), message);
607
+ const trustInfo = await TrustHelper.verifyTrust(this._trustComponent, trustPayload, "completeTransfer");
608
+ const validationFailures = await DataspaceProtocolHelper.validate(JsonLdHelper.toNodeObject(message));
609
+ if (validationFailures.length > 0) {
610
+ await this._loggingComponent?.log({
611
+ level: "error",
612
+ source: DataspaceControlPlaneService.CLASS_NAME,
613
+ ts: Date.now(),
614
+ message: "invalidTransferCompletionMessage",
615
+ data: { validationFailures }
616
+ });
617
+ return transformToTransferError(new ValidationError(DataspaceControlPlaneService.CLASS_NAME, "message", validationFailures), message);
594
618
  }
595
619
  try {
596
620
  const { entity, role } = await this.lookupTransferByMessage(message);
621
+ this.validateCallerIsConsumer(trustInfo.identity, entity);
597
622
  if (entity.state !== DataspaceProtocolTransferProcessStateType.STARTED) {
598
623
  return transformToTransferError(new GeneralError(DataspaceControlPlaneService.CLASS_NAME, "invalidStateForComplete", {
599
624
  consumerPid: entity.consumerPid,
@@ -634,14 +659,21 @@ export class DataspaceControlPlaneService {
634
659
  * @returns Transfer Process (DSP compliant) with state SUSPENDED, or TransferError if the operation fails.
635
660
  */
636
661
  async suspendTransfer(message, trustPayload) {
637
- await TrustHelper.verifyTrust(this._trustComponent, trustPayload, "suspendTransfer");
638
- const validationFailures = [];
639
- const isConformant = await DataspaceProtocolHelper.checkConformance(JsonLdHelper.toNodeObject(message), validationFailures);
640
- if (!isConformant) {
641
- return transformToTransferError(new GeneralError(DataspaceControlPlaneService.CLASS_NAME, "invalidTransferSuspensionMessage", { validationFailures }), message);
662
+ const trustInfo = await TrustHelper.verifyTrust(this._trustComponent, trustPayload, "suspendTransfer");
663
+ const validationFailures = await DataspaceProtocolHelper.validate(JsonLdHelper.toNodeObject(message));
664
+ if (validationFailures.length > 0) {
665
+ await this._loggingComponent?.log({
666
+ level: "error",
667
+ source: DataspaceControlPlaneService.CLASS_NAME,
668
+ ts: Date.now(),
669
+ message: "invalidTransferSuspensionMessage",
670
+ data: { validationFailures }
671
+ });
672
+ return transformToTransferError(new ValidationError(DataspaceControlPlaneService.CLASS_NAME, "message", validationFailures), message);
642
673
  }
643
674
  try {
644
675
  const { entity, role } = await this.lookupTransferByMessage(message);
676
+ this.validateCallerIsTransferParty(trustInfo.identity, entity);
645
677
  if (entity.state !== DataspaceProtocolTransferProcessStateType.STARTED) {
646
678
  return transformToTransferError(new GeneralError(DataspaceControlPlaneService.CLASS_NAME, "invalidStateForSuspend", {
647
679
  consumerPid: entity.consumerPid,
@@ -683,14 +715,21 @@ export class DataspaceControlPlaneService {
683
715
  * @returns Transfer Process (DSP compliant) with state TERMINATED, or TransferError if the operation fails.
684
716
  */
685
717
  async terminateTransfer(message, trustPayload) {
686
- await TrustHelper.verifyTrust(this._trustComponent, trustPayload, "terminateTransfer");
687
- const validationFailures = [];
688
- const isConformant = await DataspaceProtocolHelper.checkConformance(JsonLdHelper.toNodeObject(message), validationFailures);
689
- if (!isConformant) {
690
- return transformToTransferError(new GeneralError(DataspaceControlPlaneService.CLASS_NAME, "invalidTransferTerminationMessage", { validationFailures }), message);
718
+ const trustInfo = await TrustHelper.verifyTrust(this._trustComponent, trustPayload, "terminateTransfer");
719
+ const validationFailures = await DataspaceProtocolHelper.validate(JsonLdHelper.toNodeObject(message));
720
+ if (validationFailures.length > 0) {
721
+ await this._loggingComponent?.log({
722
+ level: "error",
723
+ source: DataspaceControlPlaneService.CLASS_NAME,
724
+ ts: Date.now(),
725
+ message: "invalidTransferTerminationMessage",
726
+ data: { validationFailures }
727
+ });
728
+ return transformToTransferError(new ValidationError(DataspaceControlPlaneService.CLASS_NAME, "message", validationFailures), message);
691
729
  }
692
730
  try {
693
731
  const { entity, role } = await this.lookupTransferByMessage(message);
732
+ this.validateCallerIsTransferParty(trustInfo.identity, entity);
694
733
  entity.state = DataspaceProtocolTransferProcessStateType.TERMINATED;
695
734
  entity.dateModified = new Date();
696
735
  await this._transferProcessStorage.set(this.modelToStorageEntity(entity));
@@ -725,9 +764,10 @@ export class DataspaceControlPlaneService {
725
764
  * @returns Transfer Process (DSP compliant) with current state, or TransferError if the operation fails.
726
765
  */
727
766
  async getTransferProcess(pid, trustPayload) {
728
- await TrustHelper.verifyTrust(this._trustComponent, trustPayload, "getTransferProcess");
767
+ const trustInfo = await TrustHelper.verifyTrust(this._trustComponent, trustPayload, "getTransferProcess");
729
768
  try {
730
769
  const { entity, role } = await this.lookupTransferByPid(pid);
770
+ this.validateCallerIsTransferParty(trustInfo.identity, entity);
731
771
  await this._loggingComponent?.log({
732
772
  level: "info",
733
773
  source: DataspaceControlPlaneService.CLASS_NAME,
@@ -759,13 +799,15 @@ export class DataspaceControlPlaneService {
759
799
  * Returns immediately with a negotiationId. The caller is notified
760
800
  * via the registered INegotiationCallback when the negotiation completes.
761
801
  *
802
+ * @param datasetId The dataset ID from the provider's catalog.
762
803
  * @param offerId The offer ID from the provider's catalog.
763
804
  * @param providerEndpoint The provider's contract negotiation endpoint URL.
764
805
  * @param publicOrigin The public origin URL of this control plane (for callbacks).
765
806
  * @param trustPayload The trust payload for authentication.
766
807
  * @returns The negotiation ID. Use the registered callback for completion notification.
767
808
  */
768
- async negotiateAgreement(offerId, providerEndpoint, publicOrigin, trustPayload) {
809
+ async negotiateAgreement(datasetId, offerId, providerEndpoint, publicOrigin, trustPayload) {
810
+ Guards.stringValue(DataspaceControlPlaneService.CLASS_NAME, "datasetId", datasetId);
769
811
  Guards.stringValue(DataspaceControlPlaneService.CLASS_NAME, "offerId", offerId);
770
812
  Guards.stringValue(DataspaceControlPlaneService.CLASS_NAME, "providerEndpoint", providerEndpoint);
771
813
  Guards.stringValue(DataspaceControlPlaneService.CLASS_NAME, "publicOrigin", publicOrigin);
@@ -774,17 +816,19 @@ export class DataspaceControlPlaneService {
774
816
  source: DataspaceControlPlaneService.CLASS_NAME,
775
817
  ts: Date.now(),
776
818
  message: "startingContractNegotiation",
777
- data: { offerId, providerEndpoint, publicOrigin }
819
+ data: { datasetId, offerId, providerEndpoint, publicOrigin }
778
820
  });
779
- const catalogResult = await this._federatedCatalogueComponent.get(offerId);
821
+ const catalogResult = await this._federatedCatalogueComponent.get(datasetId);
780
822
  if (isCatalogError(catalogResult)) {
781
823
  if (isCatalogErrorName(catalogResult, NotFoundError.CLASS_NAME)) {
782
- throw new NotFoundError(DataspaceControlPlaneService.CLASS_NAME, "datasetNotFoundInCatalog", offerId, {
824
+ throw new NotFoundError(DataspaceControlPlaneService.CLASS_NAME, "datasetNotFoundInCatalog", datasetId, {
825
+ datasetId,
783
826
  offerId,
784
827
  providerEndpoint
785
828
  });
786
829
  }
787
830
  throw new GeneralError(DataspaceControlPlaneService.CLASS_NAME, "catalogLookupFailedForNegotiation", {
831
+ datasetId,
788
832
  offerId,
789
833
  providerEndpoint,
790
834
  errorCode: catalogResult.code
@@ -960,13 +1004,13 @@ export class DataspaceControlPlaneService {
960
1004
  });
961
1005
  }
962
1006
  const assignerIdentity = OdrlPolicyHelper.extractAssignerIdentity(agreement);
963
- const assignerId = Is.array(assignerIdentity) ? assignerIdentity[0] : assignerIdentity;
964
- if (assignerId !== currentOrgId) {
1007
+ const assignerIds = ArrayHelper.fromObjectOrArray(assignerIdentity);
1008
+ if (!assignerIds.includes(currentOrgId)) {
965
1009
  throw new UnauthorizedError(DataspaceControlPlaneService.CLASS_NAME, "agreementAssignerMismatch", {
966
1010
  consumerPid,
967
1011
  agreementId: OdrlPolicyHelper.getUid(agreement),
968
1012
  expectedOrgId: currentOrgId,
969
- actualAssigner: assignerId
1013
+ actualAssigner: assignerIds.join(", ")
970
1014
  });
971
1015
  }
972
1016
  await this._loggingComponent?.log({
@@ -1020,26 +1064,27 @@ export class DataspaceControlPlaneService {
1020
1064
  }
1021
1065
  // Extract assigner UID (can be string, array, or IOdrlParty object)
1022
1066
  const assignerIdentity = OdrlPolicyHelper.extractAssignerIdentity(agreement);
1023
- const assignerId = Is.array(assignerIdentity) ? assignerIdentity[0] : assignerIdentity;
1024
- if (assignerId !== currentOrgId) {
1067
+ const assignerIds = ArrayHelper.fromObjectOrArray(assignerIdentity);
1068
+ if (!assignerIds.includes(currentOrgId)) {
1025
1069
  throw new UnauthorizedError(DataspaceControlPlaneService.CLASS_NAME, "agreementAssignerMismatchProvider", {
1026
1070
  providerPid,
1027
1071
  agreementId: OdrlPolicyHelper.getUid(agreement),
1028
1072
  expectedOrgId: currentOrgId,
1029
- actualAssigner: assignerId
1073
+ actualAssigner: assignerIds.join(", ")
1030
1074
  });
1031
1075
  }
1032
1076
  // Validate that Agreement assignee matches expected consumer identity
1033
1077
  // This ensures we're pushing to the correct consumer
1034
1078
  // If assignee is undefined but consumerIdentity has a value, this is also a mismatch
1035
1079
  const assigneeIdentity = OdrlPolicyHelper.extractAssigneeIdentity(agreement);
1036
- const assigneeId = Is.array(assigneeIdentity) ? assigneeIdentity[0] : assigneeIdentity;
1037
- if (assigneeId !== entity.consumerIdentity) {
1080
+ const assigneeIds = ArrayHelper.fromObjectOrArray(assigneeIdentity);
1081
+ if (!Is.stringValue(entity.consumerIdentity) ||
1082
+ !assigneeIds.includes(entity.consumerIdentity)) {
1038
1083
  throw new UnauthorizedError(DataspaceControlPlaneService.CLASS_NAME, "agreementAssigneeMismatch", {
1039
1084
  providerPid,
1040
1085
  agreementId: OdrlPolicyHelper.getUid(agreement),
1041
1086
  expectedConsumerIdentity: entity.consumerIdentity,
1042
- actualAssignee: assigneeId
1087
+ actualAssignee: assigneeIds.join(", ")
1043
1088
  });
1044
1089
  }
1045
1090
  await this._loggingComponent?.log({
@@ -1189,6 +1234,42 @@ export class DataspaceControlPlaneService {
1189
1234
  }
1190
1235
  return agreement;
1191
1236
  }
1237
+ /**
1238
+ * Validate that the caller's verified identity matches the consumer of a transfer process.
1239
+ * @param callerIdentity The identity from the verified trust token.
1240
+ * @param entity The transfer process entity.
1241
+ * @throws UnauthorizedError if the caller is not the consumer.
1242
+ * @internal
1243
+ */
1244
+ validateCallerIsConsumer(callerIdentity, entity) {
1245
+ if (callerIdentity !== entity.consumerIdentity) {
1246
+ throw new UnauthorizedError(DataspaceControlPlaneService.CLASS_NAME, "callerNotAuthorizedAsConsumer");
1247
+ }
1248
+ }
1249
+ /**
1250
+ * Validate that the caller's verified identity matches the provider of a transfer process.
1251
+ * @param callerIdentity The identity from the verified trust token.
1252
+ * @param entity The transfer process entity.
1253
+ * @throws UnauthorizedError if the caller is not the provider.
1254
+ * @internal
1255
+ */
1256
+ validateCallerIsProvider(callerIdentity, entity) {
1257
+ if (callerIdentity !== entity.providerIdentity) {
1258
+ throw new UnauthorizedError(DataspaceControlPlaneService.CLASS_NAME, "callerNotAuthorizedAsProvider");
1259
+ }
1260
+ }
1261
+ /**
1262
+ * Validate that the caller's verified identity matches either the consumer or provider of a transfer process.
1263
+ * @param callerIdentity The identity from the verified trust token.
1264
+ * @param entity The transfer process entity.
1265
+ * @throws UnauthorizedError if the caller is not a party to the transfer.
1266
+ * @internal
1267
+ */
1268
+ validateCallerIsTransferParty(callerIdentity, entity) {
1269
+ if (callerIdentity !== entity.consumerIdentity && callerIdentity !== entity.providerIdentity) {
1270
+ throw new UnauthorizedError(DataspaceControlPlaneService.CLASS_NAME, "callerNotAuthorizedForTransfer");
1271
+ }
1272
+ }
1192
1273
  /**
1193
1274
  * Extract dataset ID from Agreement target.
1194
1275
  * @param agreement Agreement.
@@ -1322,6 +1403,10 @@ export class DataspaceControlPlaneService {
1322
1403
  async validateAgreementMatchesOffer(agreement, catalogDataset) {
1323
1404
  Guards.object(DataspaceControlPlaneService.CLASS_NAME, "agreement", agreement);
1324
1405
  Guards.object(DataspaceControlPlaneService.CLASS_NAME, "catalogDataset", catalogDataset);
1406
+ const datasetId = getJsonLdId(catalogDataset);
1407
+ if (!Is.stringValue(datasetId)) {
1408
+ throw new NotFoundError(DataspaceControlPlaneService.CLASS_NAME, "catalogDatasetMissingId");
1409
+ }
1325
1410
  const rawOffers = this.getCatalogDatasetPolicies(catalogDataset);
1326
1411
  if (!Is.arrayValue(rawOffers)) {
1327
1412
  await this._loggingComponent?.log({
@@ -1369,15 +1454,20 @@ export class DataspaceControlPlaneService {
1369
1454
  data: {
1370
1455
  agreementId: OdrlPolicyHelper.getUid(agreement) ?? "",
1371
1456
  offerId: OdrlPolicyHelper.getUid(matchingOffer) ?? "",
1372
- datasetId: getJsonLdId(catalogDataset) ?? ""
1457
+ datasetId
1373
1458
  }
1374
1459
  });
1375
1460
  }
1376
1461
  /**
1377
1462
  * Check if an Agreement is derived from an Offer.
1463
+ * Per the DS Protocol spec, Offers within a Dataset's hasPolicy array must NOT
1464
+ * include an explicit "target" property — the target is implicitly the Dataset itself.
1465
+ * When the offer has no explicit targets, we use the datasetId as the implicit target
1466
+ * so that the comparison with the agreement's target can succeed.
1378
1467
  * @param agreement Agreement to check.
1379
1468
  * @param offer Offer to compare against.
1380
1469
  * @returns True if Agreement appears derived from Offer.
1470
+ * @see https://eclipse-dataspace-protocol-base.github.io/DataspaceProtocol/2025-1-err1/#lower-level-types
1381
1471
  * @internal
1382
1472
  */
1383
1473
  isPolicyDerivedFrom(agreement, offer) {