@twin.org/dataspace-control-plane-service 0.9.1 → 0.9.2-next.1

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.
@@ -2,11 +2,10 @@
2
2
  // SPDX-License-Identifier: Apache-2.0.
3
3
  import { HttpContextIdKeys, HttpUrlHelper } from "@twin.org/api-models";
4
4
  import { ContextIdHelper, ContextIdKeys, ContextIdStore } from "@twin.org/context";
5
- import { AlreadyExistsError, ArrayHelper, BaseError, ComponentFactory, Converter, GeneralError, Guards, Is, NotFoundError, ObjectHelper, RandomHelper, StringHelper, UnauthorizedError, Url, Urn, ValidationError } from "@twin.org/core";
5
+ import { AlreadyExistsError, ArrayHelper, BaseError, Coerce, ComponentFactory, Converter, Factory, GeneralError, Guards, Is, NotFoundError, ObjectHelper, RandomHelper, StringHelper, UnauthorizedError, Url, Urn, ValidationError } from "@twin.org/core";
6
6
  import { JsonLdHelper } from "@twin.org/data-json-ld";
7
- import { DataspaceControlPlaneMetricIds, DataspaceControlPlaneMetrics, DataspaceTransferFormat, TransferProcessRole, getJsonLdId, getJsonLdType } from "@twin.org/dataspace-models";
8
- import { EngineCoreFactory } from "@twin.org/engine-models";
9
- import { ComparisonOperator } from "@twin.org/entity";
7
+ import { DataspaceControlPlaneMetricIds, DataspaceControlPlaneMetrics, DataspaceTransferFormat, TransferProcessRole, TransferTerminationCode, getJsonLdId, getJsonLdType } from "@twin.org/dataspace-models";
8
+ import { ComparisonOperator, LogicalOperator } from "@twin.org/entity";
10
9
  import { EntityStorageConnectorFactory } from "@twin.org/entity-storage-models";
11
10
  import { OdrlPolicyHelper, PolicyRequesterFactory } from "@twin.org/rights-management-models";
12
11
  import { DataspaceProtocolContexts, DataspaceProtocolContractNegotiationTypes, DataspaceProtocolHelper, DataspaceProtocolTransferProcessStateType, DataspaceProtocolTransferProcessTypes, DataspaceProtocolVersionBindingType } from "@twin.org/standards-dataspace-protocol";
@@ -49,6 +48,11 @@ export class DataspaceControlPlaneService {
49
48
  * @internal
50
49
  */
51
50
  static _STALLED_TRANSFER_THRESHOLD_MS = 30 * 60 * 1000;
51
+ /**
52
+ * Default provider transfer policy sweep interval in milliseconds (5 minutes).
53
+ * @internal
54
+ */
55
+ static _PROVIDER_TRANSFER_POLICY_SWEEP_INTERVAL_MS = 5 * 60 * 1000;
52
56
  /**
53
57
  * The logging component.
54
58
  * @internal
@@ -171,6 +175,22 @@ export class DataspaceControlPlaneService {
171
175
  * @internal
172
176
  */
173
177
  _stalledTransferTimeoutMs;
178
+ /**
179
+ * Idle window (ms) for Provider-side STARTED PULL transfers; undefined disables the policy
180
+ * node-wide.
181
+ * @internal
182
+ */
183
+ _providerTransferIdleTimeoutMs;
184
+ /**
185
+ * Interval (ms) at which the provider transfer policy sweep runs.
186
+ * @internal
187
+ */
188
+ _providerTransferPolicySweepIntervalMs;
189
+ /**
190
+ * Storage for transfer retrievals; undefined when the hosting engine does not register it.
191
+ * @internal
192
+ */
193
+ _transferRetrievalStorage;
174
194
  /**
175
195
  * Create a new instance of DataspaceControlPlaneService.
176
196
  * @param options The options for the service.
@@ -204,6 +224,11 @@ export class DataspaceControlPlaneService {
204
224
  this._stalledTransferTimeoutMs =
205
225
  options?.config?.stalledTransferTimeoutMs ??
206
226
  DataspaceControlPlaneService._STALLED_TRANSFER_THRESHOLD_MS;
227
+ this._providerTransferIdleTimeoutMs = options?.config?.providerTransferIdleTimeoutMs;
228
+ this._providerTransferPolicySweepIntervalMs =
229
+ options?.config?.providerTransferPolicySweepIntervalMs ??
230
+ DataspaceControlPlaneService._PROVIDER_TRANSFER_POLICY_SWEEP_INTERVAL_MS;
231
+ this._transferRetrievalStorage = EntityStorageConnectorFactory.getIfExists(options?.transferRetrievalEntityStorageType ?? "transfer-retrieval");
207
232
  this._taskScheduler = ComponentFactory.getIfExists(options?.taskSchedulerComponentType ?? "task-scheduler");
208
233
  this._platformComponent = ComponentFactory.get(options?.platformComponentType ?? "platform");
209
234
  this._telemetryComponent = ComponentFactory.getIfExists(options?.telemetryComponentType);
@@ -269,10 +294,10 @@ export class DataspaceControlPlaneService {
269
294
  * @returns A promise that resolves when the federated catalogue is populated and the cleanup task is scheduled.
270
295
  */
271
296
  async start(nodeLoggingComponentType) {
272
- await MetricHelper.createMetrics(this._telemetryComponent, DataspaceControlPlaneMetrics);
273
- const engine = EngineCoreFactory.getIfExists("engine");
274
- // Skip if no engine exists OR if this is a clone instance
275
- if (Is.empty(engine) || engine.isClone()) {
297
+ const isCloneOrNoEngine = Factory.getFactory("engine-core")
298
+ ?.getIfExists("engine")
299
+ ?.isClone() ?? true;
300
+ if (isCloneOrNoEngine) {
276
301
  await this._loggingComponent?.log({
277
302
  level: "debug",
278
303
  ts: Date.now(),
@@ -281,6 +306,7 @@ export class DataspaceControlPlaneService {
281
306
  });
282
307
  return;
283
308
  }
309
+ await MetricHelper.createMetrics(this._telemetryComponent, DataspaceControlPlaneMetrics);
284
310
  if (this._taskScheduler) {
285
311
  await this._taskScheduler.addTask("control-plane-negotiation-cleanup", [
286
312
  {
@@ -298,6 +324,15 @@ export class DataspaceControlPlaneService {
298
324
  ], async () => {
299
325
  await this.cleanupStalledTransfers();
300
326
  });
327
+ await this._taskScheduler.addTask("control-plane-transfer-policy", [
328
+ {
329
+ nextTriggerTime: Date.now(),
330
+ // The scheduler has minute granularity; floor at one minute.
331
+ intervalMinutes: Math.max(1, Math.round(this._providerTransferPolicySweepIntervalMs / 60000))
332
+ }
333
+ ], async () => {
334
+ await this.applyProviderTransferPolicies();
335
+ });
301
336
  }
302
337
  }
303
338
  /**
@@ -310,6 +345,7 @@ export class DataspaceControlPlaneService {
310
345
  if (this._taskScheduler) {
311
346
  await this._taskScheduler.removeTask("control-plane-negotiation-cleanup");
312
347
  await this._taskScheduler.removeTask("control-plane-transfer-cleanup");
348
+ await this._taskScheduler.removeTask("control-plane-transfer-policy");
313
349
  }
314
350
  }
315
351
  // ----------------------------------------------------------------------------
@@ -616,6 +652,12 @@ export class DataspaceControlPlaneService {
616
652
  // CONSUMER-RECEIVE: the provider POSTed a TransferStart to our callback. Use the dataAddress
617
653
  // from the message (rebuilding it is provider work), mark our record STARTED, and notify.
618
654
  if (role === TransferProcessRole.Consumer) {
655
+ // PUSH keeps the consumer's request-time inbox; format-less legacy records need dataAddress absent for format inference.
656
+ if (!Is.empty(message.dataAddress) &&
657
+ (entity.format === DataspaceTransferFormat.HttpDataPull ||
658
+ entity.format === DataspaceTransferFormat.HttpDataPost)) {
659
+ entity.dataAddress = message.dataAddress;
660
+ }
619
661
  entity.state = DataspaceProtocolTransferProcessStateType.STARTED;
620
662
  entity.dateModified = new Date();
621
663
  await this._transferProcessStorage.set(this.modelToStorageEntity(entity));
@@ -738,7 +780,7 @@ export class DataspaceControlPlaneService {
738
780
  }
739
781
  }
740
782
  // ----------------------------------------------------------------------------
741
- // SHARED STATE MANAGEMENT OPERATIONS (Either Side)
783
+ // SHARED STATE MANAGEMENT OPERATIONS (either side, except completeTransfer: consumer-initiated only)
742
784
  // ----------------------------------------------------------------------------
743
785
  /**
744
786
  * Complete a Transfer Process.
@@ -816,6 +858,7 @@ export class DataspaceControlPlaneService {
816
858
  await this._transferProcessStorage.set(this.modelToStorageEntity(entity));
817
859
  throw teardownError;
818
860
  }
861
+ await this._transferRetrievalStorage?.remove(entity.consumerPid);
819
862
  // Completion is consumer-initiated (the auth above accepts only the consumer): a consumer→provider
820
863
  // notification, so no provider→consumer delivery here — unlike suspend/terminate (either party).
821
864
  if (role === TransferProcessRole.Consumer) {
@@ -1007,6 +1050,7 @@ export class DataspaceControlPlaneService {
1007
1050
  await this._transferProcessStorage.set(this.modelToStorageEntity(entity));
1008
1051
  throw teardownError;
1009
1052
  }
1053
+ await this._transferRetrievalStorage?.remove(entity.consumerPid);
1010
1054
  if (role === TransferProcessRole.Consumer) {
1011
1055
  await this._internalTransferCallback.onStateChanged(entity.consumerPid, DataspaceProtocolTransferProcessStateType.TERMINATED);
1012
1056
  // DSP reason is typed any[] — forward only the first entry since the callback
@@ -1070,6 +1114,67 @@ export class DataspaceControlPlaneService {
1070
1114
  return transformToTransferError(error, { consumerPid: pid, providerPid: pid });
1071
1115
  }
1072
1116
  }
1117
+ /**
1118
+ * Query Transfer Processes by agreement id.
1119
+ * Results are limited to transfers where the authenticated caller is a party
1120
+ * (consumer or provider identity).
1121
+ * @param agreementId The agreement id to look up transfer processes for.
1122
+ * @param state Optional filter to a single transfer process state.
1123
+ * @param cursor Optional pagination cursor from a previous result page.
1124
+ * @param trustPayload Trust payload containing authorization information.
1125
+ * @returns The matching transfer processes and a pagination cursor when more pages exist, empty when none match.
1126
+ */
1127
+ async queryDataTransfer(agreementId, state, cursor, trustPayload) {
1128
+ Guards.stringValue(DataspaceControlPlaneService.CLASS_NAME, "agreementId", agreementId);
1129
+ const trustInfo = await TrustHelper.verifyTrust(this._trustComponent, trustPayload, "queryDataTransfer");
1130
+ const conditions = [
1131
+ {
1132
+ property: "agreementId",
1133
+ value: agreementId,
1134
+ comparison: ComparisonOperator.Equals
1135
+ },
1136
+ {
1137
+ conditions: [
1138
+ {
1139
+ property: "consumerIdentity",
1140
+ value: trustInfo.identity,
1141
+ comparison: ComparisonOperator.Equals
1142
+ },
1143
+ {
1144
+ property: "providerIdentity",
1145
+ value: trustInfo.identity,
1146
+ comparison: ComparisonOperator.Equals
1147
+ }
1148
+ ],
1149
+ logicalOperator: LogicalOperator.Or
1150
+ }
1151
+ ];
1152
+ if (Is.stringValue(state)) {
1153
+ conditions.push({
1154
+ property: "state",
1155
+ value: state,
1156
+ comparison: ComparisonOperator.Equals
1157
+ });
1158
+ }
1159
+ const page = await this._transferProcessStorage.query({ conditions, logicalOperator: LogicalOperator.And }, undefined, undefined, cursor);
1160
+ const transfers = page.entities.map(pageEntity => this.storageEntityToModel(pageEntity));
1161
+ await this._loggingComponent?.log({
1162
+ level: "info",
1163
+ source: DataspaceControlPlaneService.CLASS_NAME,
1164
+ ts: Date.now(),
1165
+ message: "dataTransfersQueried",
1166
+ data: {
1167
+ agreementId,
1168
+ state,
1169
+ count: transfers.length,
1170
+ hasCursor: Boolean(page.cursor)
1171
+ }
1172
+ });
1173
+ return {
1174
+ transfers,
1175
+ cursor: page.cursor
1176
+ };
1177
+ }
1073
1178
  // ============================================================================
1074
1179
  // CONTRACT NEGOTIATION
1075
1180
  // ============================================================================
@@ -1398,9 +1503,12 @@ export class DataspaceControlPlaneService {
1398
1503
  * or generated.
1399
1504
  * @param appId The dataspace app this dataset belongs to.
1400
1505
  * @param dataset The dataset payload.
1506
+ * @param options Optional dataset settings.
1507
+ * @param options.transferIdleTimeoutMs Optional idle window (ms) overriding the node-level idle
1508
+ * policy for this dataset's PULL transfers; 0 disables it for this dataset.
1401
1509
  * @returns The resolved dataset id.
1402
1510
  */
1403
- async createAppDataset(id, appId, dataset) {
1511
+ async createAppDataset(id, appId, dataset, options) {
1404
1512
  Guards.stringValue(DataspaceControlPlaneService.CLASS_NAME, "appId", appId);
1405
1513
  Guards.object(DataspaceControlPlaneService.CLASS_NAME, "dataset", dataset);
1406
1514
  let resolvedId;
@@ -1431,6 +1539,7 @@ export class DataspaceControlPlaneService {
1431
1539
  tenantId: await this.resolveContextTenantId(),
1432
1540
  appId,
1433
1541
  dataset: ObjectHelper.omit(dataset, ["@id"]),
1542
+ transferIdleTimeoutMs: Coerce.integer(options?.transferIdleTimeoutMs),
1434
1543
  dateCreated: now,
1435
1544
  dateModified: now
1436
1545
  };
@@ -1459,6 +1568,7 @@ export class DataspaceControlPlaneService {
1459
1568
  id: entity.id,
1460
1569
  appId: entity.appId,
1461
1570
  dataset: this.restampDatasetId(entity.dataset, entity.id),
1571
+ transferIdleTimeoutMs: entity.transferIdleTimeoutMs,
1462
1572
  dateCreated: entity.dateCreated,
1463
1573
  dateModified: entity.dateModified
1464
1574
  };
@@ -1480,6 +1590,7 @@ export class DataspaceControlPlaneService {
1480
1590
  id: entity.id,
1481
1591
  appId: entity.appId,
1482
1592
  dataset: this.restampDatasetId(entity.dataset ?? {}, entity.id ?? ""),
1593
+ transferIdleTimeoutMs: entity.transferIdleTimeoutMs,
1483
1594
  dateCreated: entity.dateCreated,
1484
1595
  dateModified: entity.dateModified
1485
1596
  }));
@@ -1493,9 +1604,12 @@ export class DataspaceControlPlaneService {
1493
1604
  * @param id The stored dataset id.
1494
1605
  * @param appId The dataspace app this dataset belongs to.
1495
1606
  * @param dataset The dataset payload.
1607
+ * @param options Optional dataset settings.
1608
+ * @param options.transferIdleTimeoutMs Optional idle window (ms) overriding the node-level idle
1609
+ * policy for this dataset's PULL transfers; 0 disables it for this dataset.
1496
1610
  * @returns A promise that resolves when the dataset has been updated in storage and the catalogue.
1497
1611
  */
1498
- async updateAppDataset(id, appId, dataset) {
1612
+ async updateAppDataset(id, appId, dataset, options) {
1499
1613
  Guards.stringValue(DataspaceControlPlaneService.CLASS_NAME, "id", id);
1500
1614
  Guards.stringValue(DataspaceControlPlaneService.CLASS_NAME, "appId", appId);
1501
1615
  Guards.object(DataspaceControlPlaneService.CLASS_NAME, "dataset", dataset);
@@ -1511,6 +1625,7 @@ export class DataspaceControlPlaneService {
1511
1625
  ...existing,
1512
1626
  appId,
1513
1627
  dataset: ObjectHelper.omit(dataset, ["@id"]),
1628
+ transferIdleTimeoutMs: Coerce.integer(options?.transferIdleTimeoutMs),
1514
1629
  dateModified: new Date().toISOString()
1515
1630
  };
1516
1631
  // Side effect first, primary storage last
@@ -1700,6 +1815,209 @@ export class DataspaceControlPlaneService {
1700
1815
  }
1701
1816
  });
1702
1817
  }
1818
+ /**
1819
+ * Apply the idle lifecycle policy to Provider-role STARTED transfers, transitioning through
1820
+ * the standard terminateTransfer path.
1821
+ * @returns A promise that resolves when all matching transfers have been processed.
1822
+ * @internal
1823
+ */
1824
+ async applyProviderTransferPolicies() {
1825
+ const now = Date.now();
1826
+ // Runs per tenant so the storage access inherits the correct [Node, Tenant] partition.
1827
+ await this._platformComponent.execute(async () => {
1828
+ if (Is.integer(this._providerTransferIdleTimeoutMs) &&
1829
+ Is.empty(this._transferRetrievalStorage)) {
1830
+ await this._loggingComponent?.log({
1831
+ level: "warn",
1832
+ source: DataspaceControlPlaneService.CLASS_NAME,
1833
+ ts: Date.now(),
1834
+ message: "providerTransferIdleStorageMissing"
1835
+ });
1836
+ }
1837
+ const providerStarted = [];
1838
+ let cursor;
1839
+ do {
1840
+ // The localRole condition also skips legacy records without a persisted localRole.
1841
+ const page = await this._transferProcessStorage.query({
1842
+ conditions: [
1843
+ {
1844
+ property: "state",
1845
+ value: DataspaceProtocolTransferProcessStateType.STARTED,
1846
+ comparison: ComparisonOperator.Equals
1847
+ },
1848
+ {
1849
+ property: "localRole",
1850
+ value: TransferProcessRole.Provider,
1851
+ comparison: ComparisonOperator.Equals
1852
+ }
1853
+ ],
1854
+ logicalOperator: LogicalOperator.And
1855
+ }, undefined, undefined, cursor);
1856
+ providerStarted.push(...page.entities);
1857
+ cursor = page.cursor;
1858
+ } while (Is.stringValue(cursor));
1859
+ const datasetIdleCache = new Map();
1860
+ let terminated = 0;
1861
+ for (const transfer of providerStarted) {
1862
+ if (!Is.stringValue(transfer.providerIdentity)) {
1863
+ // Cannot be transitioned without a provider identity, so remove it directly.
1864
+ await this._transferProcessStorage.remove(transfer.consumerPid);
1865
+ await this._loggingComponent?.log({
1866
+ level: "warn",
1867
+ source: DataspaceControlPlaneService.CLASS_NAME,
1868
+ ts: Date.now(),
1869
+ message: "providerTransferPolicyNoProviderIdentity",
1870
+ data: { consumerPid: transfer.consumerPid }
1871
+ });
1872
+ }
1873
+ else if (await this.terminateIdleProviderTransfer(transfer, now, datasetIdleCache)) {
1874
+ terminated++;
1875
+ }
1876
+ }
1877
+ if (terminated > 0) {
1878
+ await this._loggingComponent?.log({
1879
+ level: "info",
1880
+ source: DataspaceControlPlaneService.CLASS_NAME,
1881
+ ts: Date.now(),
1882
+ message: "providerTransferPolicySweepComplete",
1883
+ data: { terminated }
1884
+ });
1885
+ }
1886
+ });
1887
+ }
1888
+ /**
1889
+ * Terminate a Provider-side PULL transfer idle beyond its effective window (dataset override,
1890
+ * falling back to the node config; 0 disables). Activity is the later of the last state change
1891
+ * and the last successful retrieval. Notifies the registered callbacks (onTimeout, falling
1892
+ * back to onFailed).
1893
+ * @param transfer The transfer process entity.
1894
+ * @param now The sweep timestamp.
1895
+ * @param datasetIdleCache Per-sweep cache of dataset overrides.
1896
+ * @returns True when the transfer was terminated.
1897
+ * @internal
1898
+ */
1899
+ async terminateIdleProviderTransfer(transfer, now, datasetIdleCache) {
1900
+ // Retrieval activity is invisible without the storage, so skip rather than cut off
1901
+ // consumers that are actively pulling.
1902
+ if (this.inferTransferFormat(transfer) !== DataspaceTransferFormat.HttpDataPull ||
1903
+ Is.empty(this._transferRetrievalStorage)) {
1904
+ return false;
1905
+ }
1906
+ let datasetIdleMs;
1907
+ if (datasetIdleCache.has(transfer.datasetId)) {
1908
+ datasetIdleMs = datasetIdleCache.get(transfer.datasetId);
1909
+ }
1910
+ else {
1911
+ const dataset = await this._dataspaceAppDatasetStorage.get(transfer.datasetId);
1912
+ datasetIdleMs = dataset?.transferIdleTimeoutMs;
1913
+ datasetIdleCache.set(transfer.datasetId, datasetIdleMs);
1914
+ }
1915
+ const idleMs = datasetIdleMs ?? this._providerTransferIdleTimeoutMs;
1916
+ if (!Is.integer(idleMs) || idleMs === 0) {
1917
+ return false;
1918
+ }
1919
+ const retrieval = await this._transferRetrievalStorage.get(transfer.consumerPid);
1920
+ const lastRetrieved = retrieval?.dateLastRetrieved;
1921
+ const lastActivity = Math.max(new Date(transfer.dateModified).getTime(), Is.stringValue(lastRetrieved) ? new Date(lastRetrieved).getTime() : 0);
1922
+ if (now - lastActivity <= idleMs) {
1923
+ return false;
1924
+ }
1925
+ const consumerPid = transfer.consumerPid;
1926
+ await this._loggingComponent?.log({
1927
+ level: "warn",
1928
+ source: DataspaceControlPlaneService.CLASS_NAME,
1929
+ ts: Date.now(),
1930
+ message: "providerTransferIdleTimedOut",
1931
+ data: {
1932
+ consumerPid,
1933
+ providerPid: transfer.providerPid,
1934
+ idleTimeoutMs: idleMs
1935
+ }
1936
+ });
1937
+ for (const [key, cb] of this._transferCallbacks.entries()) {
1938
+ const onTimeout = cb.onTimeout?.bind(cb);
1939
+ const onFailed = cb.onFailed?.bind(cb);
1940
+ try {
1941
+ if (Is.function(onTimeout)) {
1942
+ await onTimeout(consumerPid);
1943
+ }
1944
+ else if (Is.function(onFailed)) {
1945
+ await onFailed(consumerPid, TransferTerminationCode.IdleTimeout);
1946
+ }
1947
+ }
1948
+ catch (error) {
1949
+ await this._loggingComponent?.log({
1950
+ level: "error",
1951
+ source: DataspaceControlPlaneService.CLASS_NAME,
1952
+ ts: Date.now(),
1953
+ message: "transferCallbackError",
1954
+ data: {
1955
+ key,
1956
+ consumerPid,
1957
+ method: Is.function(onTimeout) ? "onTimeout" : "onFailed",
1958
+ error
1959
+ }
1960
+ });
1961
+ }
1962
+ }
1963
+ return this.terminateProviderPolicyTransfer(transfer, TransferTerminationCode.IdleTimeout);
1964
+ }
1965
+ /**
1966
+ * Terminate a Provider-side transfer for a lifecycle policy: a self-issued provider token and
1967
+ * the owning organization context (the per-tenant execute establishes only the tenant), then
1968
+ * the standard terminateTransfer path. Failures are logged, never thrown.
1969
+ * @param transfer The transfer process entity.
1970
+ * @param code The termination code, also forwarded as the reason.
1971
+ * @returns True when the transfer was terminated.
1972
+ * @internal
1973
+ */
1974
+ async terminateProviderPolicyTransfer(transfer, code) {
1975
+ const consumerPid = transfer.consumerPid;
1976
+ if (!Is.stringValue(transfer.providerIdentity)) {
1977
+ return false;
1978
+ }
1979
+ try {
1980
+ const selfToken = await this._trustComponent.generate(transfer.providerIdentity, this._overrideTrustGeneratorType, {
1981
+ subject: {
1982
+ consumerPid,
1983
+ providerPid: transfer.providerPid,
1984
+ agreementId: transfer.agreementId
1985
+ }
1986
+ });
1987
+ const terminationMessage = {
1988
+ "@context": [DataspaceProtocolContexts.Context],
1989
+ "@type": DataspaceProtocolTransferProcessTypes.TransferTerminationMessage,
1990
+ consumerPid,
1991
+ providerPid: transfer.providerPid,
1992
+ code,
1993
+ reason: [code]
1994
+ };
1995
+ const contextIds = await ContextIdStore.getContextIds();
1996
+ const result = await ContextIdStore.run({ ...contextIds, [ContextIdKeys.Organization]: transfer.organizationIdentity }, async () => this.terminateTransfer(terminationMessage, selfToken));
1997
+ if (getJsonLdType(result) === DataspaceProtocolTransferProcessTypes.TransferError) {
1998
+ await this._loggingComponent?.log({
1999
+ level: "error",
2000
+ source: DataspaceControlPlaneService.CLASS_NAME,
2001
+ ts: Date.now(),
2002
+ message: "providerTransferTerminateFailed",
2003
+ data: { consumerPid, code: result.code }
2004
+ });
2005
+ return false;
2006
+ }
2007
+ return true;
2008
+ }
2009
+ catch (error) {
2010
+ await this._loggingComponent?.log({
2011
+ level: "error",
2012
+ source: DataspaceControlPlaneService.CLASS_NAME,
2013
+ ts: Date.now(),
2014
+ message: "providerTransferTerminateFailed",
2015
+ error: BaseError.fromError(error),
2016
+ data: { consumerPid }
2017
+ });
2018
+ return false;
2019
+ }
2020
+ }
1703
2021
  /**
1704
2022
  * Perform the provider-side start of a just-requested transfer (build dataAddress, transition to
1705
2023
  * STARTED, deliver to the consumer). Only invoked when the request opted into auto-start, so there is no