@twin.org/dataspace-control-plane-service 0.0.3-next.45 → 0.0.3-next.46
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/dist/es/dataspaceControlPlaneRoutes.js +2 -1
- package/dist/es/dataspaceControlPlaneRoutes.js.map +1 -1
- package/dist/es/dataspaceControlPlaneService.js +217 -31
- package/dist/es/dataspaceControlPlaneService.js.map +1 -1
- package/dist/es/models/IDataspaceControlPlaneServiceConfig.js.map +1 -1
- package/dist/types/dataspaceControlPlaneService.d.ts +10 -3
- package/dist/types/models/IDataspaceControlPlaneServiceConfig.d.ts +8 -0
- package/docs/changelog.md +14 -0
- package/docs/reference/classes/DataspaceControlPlaneService.md +35 -5
- package/docs/reference/interfaces/IDataspaceControlPlaneServiceConfig.md +12 -0
- package/locales/en.json +6 -2
- package/package.json +2 -2
|
@@ -93,6 +93,12 @@ export class DataspaceControlPlaneService {
|
|
|
93
93
|
* @internal
|
|
94
94
|
*/
|
|
95
95
|
_dataPlanePath;
|
|
96
|
+
/**
|
|
97
|
+
* Control plane callback mount path (path only, not full URL). Combined with this node's public
|
|
98
|
+
* origin to form the consumer callbackAddress the provider POSTs DSP transfer messages back to.
|
|
99
|
+
* @internal
|
|
100
|
+
*/
|
|
101
|
+
_callbackPath;
|
|
96
102
|
/**
|
|
97
103
|
* Factory key used to look up the data plane component. Resolved lazily at push-time
|
|
98
104
|
* (not at construction) so the data plane may register after the control plane is built.
|
|
@@ -158,6 +164,9 @@ export class DataspaceControlPlaneService {
|
|
|
158
164
|
this._dataPlanePath = Is.stringValue(options?.config?.dataPlanePath)
|
|
159
165
|
? StringHelper.trimTrailingSlashes(StringHelper.trimLeadingSlashes(options.config.dataPlanePath))
|
|
160
166
|
: undefined;
|
|
167
|
+
this._callbackPath = Is.stringValue(options?.config?.callbackPath)
|
|
168
|
+
? StringHelper.trimLeadingAndTrailingSlashes(options.config.callbackPath)
|
|
169
|
+
: undefined;
|
|
161
170
|
this._taskScheduler = ComponentFactory.getIfExists(options?.taskSchedulerComponentType ?? "task-scheduler");
|
|
162
171
|
this._platformComponent = ComponentFactory.getIfExists(options?.platformComponentType ?? "platform");
|
|
163
172
|
// Data plane component is optional and resolved lazily. The control plane is initialised
|
|
@@ -332,13 +341,18 @@ export class DataspaceControlPlaneService {
|
|
|
332
341
|
* Request a Transfer Process.
|
|
333
342
|
* Creates a new Transfer Process in REQUESTED state.
|
|
334
343
|
* @param request Transfer request message (DSP compliant).
|
|
344
|
+
* @param publicOrigin The public origin of this provider node, resolved by the REST route from the
|
|
345
|
+
* hosting component; used to build the data-plane endpoint when auto-starting.
|
|
346
|
+
* @param options Request options.
|
|
347
|
+
* @param options.autoStart When true, the provider immediately starts the requested transfer (scheduled
|
|
348
|
+
* on the next tick); when omitted/false the provider start must be triggered explicitly.
|
|
335
349
|
* @param trustPayload Trust payload containing authorization information (Base64-encoded token).
|
|
336
350
|
* @returns Transfer Process (DSP compliant) with state REQUESTED, or TransferError if the operation fails.
|
|
337
351
|
*
|
|
338
352
|
* Role Performed: Provider
|
|
339
353
|
* Called by: Consumer when it wants to request a new Transfer Process
|
|
340
354
|
*/
|
|
341
|
-
async requestTransfer(request, trustPayload) {
|
|
355
|
+
async requestTransfer(request, publicOrigin, options, trustPayload) {
|
|
342
356
|
const trustInfo = await TrustHelper.verifyTrust(this._trustComponent, trustPayload, "requestTransfer");
|
|
343
357
|
const validationFailures = await DataspaceProtocolHelper.validate(JsonLdHelper.toNodeObject(request));
|
|
344
358
|
if (Is.arrayValue(validationFailures)) {
|
|
@@ -405,6 +419,7 @@ export class DataspaceControlPlaneService {
|
|
|
405
419
|
datasetId,
|
|
406
420
|
consumerIdentity,
|
|
407
421
|
providerIdentity,
|
|
422
|
+
localRole: TransferProcessRole.Provider,
|
|
408
423
|
// offerId should reference Catalog Offer (via Agreement)
|
|
409
424
|
// For now, use agreementId as reference (proper flow: Catalog → Negotiation → Agreement)
|
|
410
425
|
offerId: request.agreementId,
|
|
@@ -429,6 +444,16 @@ export class DataspaceControlPlaneService {
|
|
|
429
444
|
agreementId: request.agreementId
|
|
430
445
|
}
|
|
431
446
|
});
|
|
447
|
+
// When auto-start is requested, schedule the provider start asynchronously (mirrors negotiation's
|
|
448
|
+
// setTimeout follow-up). The timer runs inside the request's ALS context, so the [Node, Tenant] + org
|
|
449
|
+
// partition propagates to the deferred start.
|
|
450
|
+
if (options?.autoStart) {
|
|
451
|
+
const consumerPid = request.consumerPid;
|
|
452
|
+
setTimeout(async () => {
|
|
453
|
+
// runProviderStart catches internally and can't reject; awaiting satisfies no-floating-promises.
|
|
454
|
+
await this.runProviderStart(consumerPid, publicOrigin);
|
|
455
|
+
}, 0);
|
|
456
|
+
}
|
|
432
457
|
return {
|
|
433
458
|
"@context": [DataspaceProtocolContexts.Context],
|
|
434
459
|
"@type": DataspaceProtocolTransferProcessTypes.TransferProcess,
|
|
@@ -438,7 +463,7 @@ export class DataspaceControlPlaneService {
|
|
|
438
463
|
};
|
|
439
464
|
}
|
|
440
465
|
/**
|
|
441
|
-
*
|
|
466
|
+
* Prepare a data transfer as a Consumer.
|
|
442
467
|
* Generates a consumerPid, POSTs a TransferRequestMessage to the provider's DSP endpoint,
|
|
443
468
|
* and (only if the provider accepts) persists a local TransferProcess in REQUESTED state.
|
|
444
469
|
* @param agreementId The finalized agreement ID from contract negotiation.
|
|
@@ -456,7 +481,7 @@ export class DataspaceControlPlaneService {
|
|
|
456
481
|
* registration ignores the runtime `endpoint` arg and silently POSTs to its
|
|
457
482
|
* static endpoint instead.
|
|
458
483
|
*/
|
|
459
|
-
async
|
|
484
|
+
async prepareTransfer(agreementId, providerEndpoint, publicOrigin, format, trustPayload) {
|
|
460
485
|
Guards.stringValue(DataspaceControlPlaneService.CLASS_NAME, "agreementId", agreementId);
|
|
461
486
|
Guards.stringValue(DataspaceControlPlaneService.CLASS_NAME, "providerEndpoint", providerEndpoint);
|
|
462
487
|
Guards.stringValue(DataspaceControlPlaneService.CLASS_NAME, "publicOrigin", publicOrigin);
|
|
@@ -467,12 +492,12 @@ export class DataspaceControlPlaneService {
|
|
|
467
492
|
supported: Object.values(DataspaceTransferFormat)
|
|
468
493
|
});
|
|
469
494
|
}
|
|
470
|
-
const trustInfo = await TrustHelper.verifyTrust(this._trustComponent, trustPayload, "
|
|
495
|
+
const trustInfo = await TrustHelper.verifyTrust(this._trustComponent, trustPayload, "prepareTransfer");
|
|
471
496
|
await this._loggingComponent?.log({
|
|
472
497
|
level: "info",
|
|
473
498
|
source: DataspaceControlPlaneService.CLASS_NAME,
|
|
474
499
|
ts: Date.now(),
|
|
475
|
-
message: "
|
|
500
|
+
message: "preparingTransfer",
|
|
476
501
|
data: { agreementId, providerEndpoint, format, identity: trustInfo.identity }
|
|
477
502
|
});
|
|
478
503
|
const agreement = await this.lookupAgreement(agreementId);
|
|
@@ -489,9 +514,18 @@ export class DataspaceControlPlaneService {
|
|
|
489
514
|
const providerIdentity = assignerIds[0];
|
|
490
515
|
const datasetId = this.extractDatasetId(agreement);
|
|
491
516
|
const consumerPid = `urn:uuid:${RandomHelper.generateUuidV7()}`;
|
|
492
|
-
const
|
|
493
|
-
// Fetch context once and reuse across the PUSH dataAddress, trust token, and storage
|
|
517
|
+
const origin = StringHelper.trimTrailingSlashes(publicOrigin);
|
|
518
|
+
// Fetch context once and reuse across the callback, PUSH dataAddress, trust token, and storage.
|
|
494
519
|
const organizationIdentity = await this.resolveContextOrganizationId();
|
|
520
|
+
// Callback the provider POSTs DSP messages back to: mount path + `?organization=` so the inbound
|
|
521
|
+
// POST routes to the right consumer tenant (mirrors buildCallbackUrl). The bare `origin` is kept for
|
|
522
|
+
// the PUSH-inbox base below (a different mount).
|
|
523
|
+
let callbackAddress = Is.stringValue(this._callbackPath)
|
|
524
|
+
? `${origin}/${this._callbackPath}`
|
|
525
|
+
: origin;
|
|
526
|
+
if (Is.stringValue(organizationIdentity)) {
|
|
527
|
+
callbackAddress = HttpUrlHelper.addQueryStringParam(callbackAddress, ContextIdKeys.Organization, organizationIdentity);
|
|
528
|
+
}
|
|
495
529
|
const transferRequestMessage = {
|
|
496
530
|
"@context": [DataspaceProtocolContexts.Context],
|
|
497
531
|
"@type": DataspaceProtocolTransferProcessTypes.TransferRequestMessage,
|
|
@@ -507,7 +541,7 @@ export class DataspaceControlPlaneService {
|
|
|
507
541
|
if (!Is.stringValue(this._dataPlanePath)) {
|
|
508
542
|
throw new GeneralError(DataspaceControlPlaneService.CLASS_NAME, "pushTransferDataPathNotConfigured", { consumerPid });
|
|
509
543
|
}
|
|
510
|
-
let inboxEndpoint = `${
|
|
544
|
+
let inboxEndpoint = `${origin}/${this._dataPlanePath}/inbox`;
|
|
511
545
|
if (Is.stringValue(organizationIdentity)) {
|
|
512
546
|
inboxEndpoint = HttpUrlHelper.addQueryStringParam(inboxEndpoint, ContextIdKeys.Organization, organizationIdentity);
|
|
513
547
|
}
|
|
@@ -521,7 +555,7 @@ export class DataspaceControlPlaneService {
|
|
|
521
555
|
const outboundToken = await this._trustComponent.generate(organizationIdentity, this._overrideTrustGeneratorType, { subject: { consumerPid, agreementId } });
|
|
522
556
|
// Create a remote REST client pointed at the provider endpoint and call requestTransfer.
|
|
523
557
|
const remoteControlPlane = ComponentFactory.create(this._remoteControlPlaneComponentType, { endpoint: providerEndpoint, pathPrefix: "" });
|
|
524
|
-
const result = await remoteControlPlane.requestTransfer(transferRequestMessage, outboundToken);
|
|
558
|
+
const result = await remoteControlPlane.requestTransfer(transferRequestMessage, "", undefined, outboundToken);
|
|
525
559
|
if (getJsonLdType(result) === DataspaceProtocolTransferProcessTypes.TransferError) {
|
|
526
560
|
const transferError = result;
|
|
527
561
|
throw new GeneralError(DataspaceControlPlaneService.CLASS_NAME, "transferRequestRejectedByProvider", {
|
|
@@ -544,6 +578,7 @@ export class DataspaceControlPlaneService {
|
|
|
544
578
|
datasetId,
|
|
545
579
|
consumerIdentity: trustInfo.identity,
|
|
546
580
|
providerIdentity,
|
|
581
|
+
localRole: TransferProcessRole.Consumer,
|
|
547
582
|
offerId: agreementId,
|
|
548
583
|
policies: [agreement],
|
|
549
584
|
callbackAddress,
|
|
@@ -558,7 +593,7 @@ export class DataspaceControlPlaneService {
|
|
|
558
593
|
level: "info",
|
|
559
594
|
source: DataspaceControlPlaneService.CLASS_NAME,
|
|
560
595
|
ts: Date.now(),
|
|
561
|
-
message: "
|
|
596
|
+
message: "transferPrepared",
|
|
562
597
|
data: {
|
|
563
598
|
consumerPid,
|
|
564
599
|
providerPid: storageEntity.providerPid,
|
|
@@ -613,6 +648,28 @@ export class DataspaceControlPlaneService {
|
|
|
613
648
|
currentState: entity.state
|
|
614
649
|
}), message);
|
|
615
650
|
}
|
|
651
|
+
// CONSUMER-RECEIVE: the provider POSTed a TransferStart to our callback. Use the dataAddress
|
|
652
|
+
// from the message (rebuilding it is provider work), mark our record STARTED, and notify.
|
|
653
|
+
if (role === TransferProcessRole.Consumer) {
|
|
654
|
+
entity.state = DataspaceProtocolTransferProcessStateType.STARTED;
|
|
655
|
+
entity.dateModified = new Date();
|
|
656
|
+
await this._transferProcessStorage.set(this.modelToStorageEntity(entity));
|
|
657
|
+
await this._loggingComponent?.log({
|
|
658
|
+
level: "info",
|
|
659
|
+
source: DataspaceControlPlaneService.CLASS_NAME,
|
|
660
|
+
ts: Date.now(),
|
|
661
|
+
message: "transferProcessStarted",
|
|
662
|
+
data: {
|
|
663
|
+
consumerPid: entity.consumerPid,
|
|
664
|
+
providerPid: entity.providerPid,
|
|
665
|
+
role
|
|
666
|
+
}
|
|
667
|
+
});
|
|
668
|
+
await this._internalTransferCallback.onStateChanged(entity.consumerPid, DataspaceProtocolTransferProcessStateType.STARTED);
|
|
669
|
+
await this._internalTransferCallback.onStarted(entity.consumerPid, message);
|
|
670
|
+
return message;
|
|
671
|
+
}
|
|
672
|
+
// PROVIDER-ACT (role === Provider): build the dataAddress, transition to STARTED, deliver below.
|
|
616
673
|
// The previous (pre-transition) state drives the push-subscription branch below
|
|
617
674
|
// (setup vs resume). Do NOT mutate entity.state here as the persisted transition
|
|
618
675
|
// to STARTED happens after the dispatch block succeeds, so any validation or
|
|
@@ -829,10 +886,9 @@ export class DataspaceControlPlaneService {
|
|
|
829
886
|
role
|
|
830
887
|
}
|
|
831
888
|
});
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
}
|
|
889
|
+
// Notify the consumer by POSTing the TransferStart (with its dataAddress) to the callback.
|
|
890
|
+
// Best-effort: a delivery failure must not roll back the STARTED transition.
|
|
891
|
+
await this.deliverToConsumerCallback(entity, "start", async (remoteControlPlane, token) => remoteControlPlane.startTransfer(response, "", token));
|
|
836
892
|
return response;
|
|
837
893
|
}
|
|
838
894
|
catch (error) {
|
|
@@ -919,6 +975,8 @@ export class DataspaceControlPlaneService {
|
|
|
919
975
|
throw teardownError;
|
|
920
976
|
}
|
|
921
977
|
}
|
|
978
|
+
// Completion is consumer-initiated (the auth above accepts only the consumer): a consumer→provider
|
|
979
|
+
// notification, so no provider→consumer delivery here — unlike suspend/terminate (either party).
|
|
922
980
|
if (role === TransferProcessRole.Consumer) {
|
|
923
981
|
await this._internalTransferCallback.onStateChanged(entity.consumerPid, DataspaceProtocolTransferProcessStateType.COMPLETED);
|
|
924
982
|
await this._internalTransferCallback.onCompleted(entity.consumerPid);
|
|
@@ -1021,6 +1079,10 @@ export class DataspaceControlPlaneService {
|
|
|
1021
1079
|
: message.reason;
|
|
1022
1080
|
await this._internalTransferCallback.onSuspended(entity.consumerPid, suspendReason);
|
|
1023
1081
|
}
|
|
1082
|
+
else {
|
|
1083
|
+
// PROVIDER-ACT: forward the suspension (with its reason) to the consumer callback.
|
|
1084
|
+
await this.deliverToConsumerCallback(entity, "suspension", async (remoteControlPlane, token) => remoteControlPlane.suspendTransfer(message, token));
|
|
1085
|
+
}
|
|
1024
1086
|
return {
|
|
1025
1087
|
"@context": [DataspaceProtocolContexts.Context],
|
|
1026
1088
|
"@type": DataspaceProtocolTransferProcessTypes.TransferProcess,
|
|
@@ -1113,6 +1175,10 @@ export class DataspaceControlPlaneService {
|
|
|
1113
1175
|
: message.reason;
|
|
1114
1176
|
await this._internalTransferCallback.onTerminated(entity.consumerPid, terminateReason);
|
|
1115
1177
|
}
|
|
1178
|
+
else {
|
|
1179
|
+
// PROVIDER-ACT: forward the termination (with its reason) to the consumer callback.
|
|
1180
|
+
await this.deliverToConsumerCallback(entity, "termination", async (remoteControlPlane, token) => remoteControlPlane.terminateTransfer(message, token));
|
|
1181
|
+
}
|
|
1116
1182
|
return {
|
|
1117
1183
|
"@context": [DataspaceProtocolContexts.Context],
|
|
1118
1184
|
"@type": DataspaceProtocolTransferProcessTypes.TransferProcess,
|
|
@@ -1676,6 +1742,122 @@ export class DataspaceControlPlaneService {
|
|
|
1676
1742
|
});
|
|
1677
1743
|
}
|
|
1678
1744
|
}
|
|
1745
|
+
/**
|
|
1746
|
+
* Perform the provider-side start of a just-requested transfer (build dataAddress, transition to
|
|
1747
|
+
* STARTED, deliver to the consumer). Only invoked when the request opted into auto-start, so there is no
|
|
1748
|
+
* approval step. Self-contained: failures are logged, never thrown. Runs inside the request's ALS
|
|
1749
|
+
* context, so the deferred storage access inherits the [Node, Tenant] + org partition.
|
|
1750
|
+
* @param consumerPid The consumerPid of the requested transfer.
|
|
1751
|
+
* @param publicOrigin This provider node's public origin, used to build the data-plane endpoint.
|
|
1752
|
+
* @internal
|
|
1753
|
+
*/
|
|
1754
|
+
async runProviderStart(consumerPid, publicOrigin) {
|
|
1755
|
+
try {
|
|
1756
|
+
const { entity } = await this.lookupTransferByPid(consumerPid);
|
|
1757
|
+
if (entity.state !== DataspaceProtocolTransferProcessStateType.REQUESTED) {
|
|
1758
|
+
// Already advanced (e.g. an explicit start raced the auto-start). Nothing to do.
|
|
1759
|
+
return;
|
|
1760
|
+
}
|
|
1761
|
+
if (!Is.stringValue(entity.providerIdentity)) {
|
|
1762
|
+
throw new GeneralError(DataspaceControlPlaneService.CLASS_NAME, "providerIdentityMissing");
|
|
1763
|
+
}
|
|
1764
|
+
// Fail closed: without a public origin the data-plane endpoint in the dataAddress would be
|
|
1765
|
+
// broken, so hold the transfer rather than ship a relative/empty endpoint to the consumer.
|
|
1766
|
+
if (!Is.stringValue(publicOrigin)) {
|
|
1767
|
+
await this._loggingComponent?.log({
|
|
1768
|
+
level: "error",
|
|
1769
|
+
source: DataspaceControlPlaneService.CLASS_NAME,
|
|
1770
|
+
ts: Date.now(),
|
|
1771
|
+
message: "autoStartPublicOriginMissing",
|
|
1772
|
+
data: { consumerPid }
|
|
1773
|
+
});
|
|
1774
|
+
return;
|
|
1775
|
+
}
|
|
1776
|
+
// Self-token issued as the provider so startTransfer's provider-auth check passes.
|
|
1777
|
+
const selfToken = await this._trustComponent.generate(entity.providerIdentity, this._overrideTrustGeneratorType, {
|
|
1778
|
+
subject: {
|
|
1779
|
+
consumerPid: entity.consumerPid,
|
|
1780
|
+
providerPid: entity.providerPid,
|
|
1781
|
+
agreementId: entity.agreementId
|
|
1782
|
+
}
|
|
1783
|
+
});
|
|
1784
|
+
const startMessage = {
|
|
1785
|
+
"@context": [DataspaceProtocolContexts.Context],
|
|
1786
|
+
"@type": DataspaceProtocolTransferProcessTypes.TransferStartMessage,
|
|
1787
|
+
consumerPid: entity.consumerPid,
|
|
1788
|
+
providerPid: entity.providerPid
|
|
1789
|
+
};
|
|
1790
|
+
await this.startTransfer(startMessage, publicOrigin, selfToken);
|
|
1791
|
+
}
|
|
1792
|
+
catch (error) {
|
|
1793
|
+
await this._loggingComponent?.log({
|
|
1794
|
+
level: "error",
|
|
1795
|
+
source: DataspaceControlPlaneService.CLASS_NAME,
|
|
1796
|
+
ts: Date.now(),
|
|
1797
|
+
message: "autoStartFailed",
|
|
1798
|
+
error: BaseError.fromError(error),
|
|
1799
|
+
data: { consumerPid }
|
|
1800
|
+
});
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
/**
|
|
1804
|
+
* Deliver a transfer state-change DSP message to the consumer's callback (provider → consumer), via a
|
|
1805
|
+
* remote control-plane client and the supplied sender. Mirrors negotiation's sendOfferToConsumer.
|
|
1806
|
+
* Best-effort: failures are logged, not thrown, so delivery problems don't roll back the transition.
|
|
1807
|
+
* @param entity The transfer process (provider side).
|
|
1808
|
+
* @param messageKind Short label for logging (e.g. "start", "suspension", "termination").
|
|
1809
|
+
* @param send Performs the remote POST given the remote client and an outbound trust token.
|
|
1810
|
+
* @internal
|
|
1811
|
+
*/
|
|
1812
|
+
async deliverToConsumerCallback(entity, messageKind, send) {
|
|
1813
|
+
if (!Is.stringValue(entity.callbackAddress) || !Is.stringValue(entity.providerIdentity)) {
|
|
1814
|
+
// Spec-allowed: no callback supplied → the consumer polls GET /transfers/:pid instead.
|
|
1815
|
+
return;
|
|
1816
|
+
}
|
|
1817
|
+
try {
|
|
1818
|
+
// Issue the token as the provider party (the agreement assigner) — the identity the consumer's
|
|
1819
|
+
// receive gate checks (`!== providerIdentity`), not the tenant-routing org. Keeps the two
|
|
1820
|
+
// identity spaces separate and works whether or not a node's org == its assigner DID.
|
|
1821
|
+
const outboundToken = await this._trustComponent.generate(entity.providerIdentity, this._overrideTrustGeneratorType, {
|
|
1822
|
+
subject: {
|
|
1823
|
+
consumerPid: entity.consumerPid,
|
|
1824
|
+
providerPid: entity.providerPid,
|
|
1825
|
+
agreementId: entity.agreementId
|
|
1826
|
+
}
|
|
1827
|
+
});
|
|
1828
|
+
const remoteControlPlane = ComponentFactory.create(this._remoteControlPlaneComponentType, { endpoint: entity.callbackAddress, pathPrefix: "" });
|
|
1829
|
+
const result = await send(remoteControlPlane, outboundToken);
|
|
1830
|
+
if (getJsonLdType(result) === DataspaceProtocolTransferProcessTypes.TransferError) {
|
|
1831
|
+
await this._loggingComponent?.log({
|
|
1832
|
+
level: "error",
|
|
1833
|
+
source: DataspaceControlPlaneService.CLASS_NAME,
|
|
1834
|
+
ts: Date.now(),
|
|
1835
|
+
message: "transferCallbackRejected",
|
|
1836
|
+
data: {
|
|
1837
|
+
messageKind,
|
|
1838
|
+
consumerPid: entity.consumerPid,
|
|
1839
|
+
providerPid: entity.providerPid,
|
|
1840
|
+
callbackAddress: entity.callbackAddress
|
|
1841
|
+
}
|
|
1842
|
+
});
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
catch (error) {
|
|
1846
|
+
await this._loggingComponent?.log({
|
|
1847
|
+
level: "error",
|
|
1848
|
+
source: DataspaceControlPlaneService.CLASS_NAME,
|
|
1849
|
+
ts: Date.now(),
|
|
1850
|
+
message: "transferCallbackFailed",
|
|
1851
|
+
error: BaseError.fromError(error),
|
|
1852
|
+
data: {
|
|
1853
|
+
messageKind,
|
|
1854
|
+
consumerPid: entity.consumerPid,
|
|
1855
|
+
providerPid: entity.providerPid,
|
|
1856
|
+
callbackAddress: entity.callbackAddress
|
|
1857
|
+
}
|
|
1858
|
+
});
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1679
1861
|
/**
|
|
1680
1862
|
* Convert a storage entity to model.
|
|
1681
1863
|
* @param storageEntity The entity from storage.
|
|
@@ -1693,6 +1875,7 @@ export class DataspaceControlPlaneService {
|
|
|
1693
1875
|
offerId: storageEntity.offerId,
|
|
1694
1876
|
consumerIdentity: storageEntity.consumerIdentity,
|
|
1695
1877
|
providerIdentity: storageEntity.providerIdentity,
|
|
1878
|
+
localRole: storageEntity.localRole,
|
|
1696
1879
|
format: storageEntity.format,
|
|
1697
1880
|
callbackAddress: storageEntity.callbackAddress,
|
|
1698
1881
|
organizationIdentity: storageEntity.organizationIdentity,
|
|
@@ -1719,6 +1902,7 @@ export class DataspaceControlPlaneService {
|
|
|
1719
1902
|
offerId: entity.offerId,
|
|
1720
1903
|
consumerIdentity: entity.consumerIdentity,
|
|
1721
1904
|
providerIdentity: entity.providerIdentity,
|
|
1905
|
+
localRole: entity.localRole,
|
|
1722
1906
|
format: entity.format,
|
|
1723
1907
|
callbackAddress: entity.callbackAddress,
|
|
1724
1908
|
organizationIdentity: entity.organizationIdentity,
|
|
@@ -1847,25 +2031,27 @@ export class DataspaceControlPlaneService {
|
|
|
1847
2031
|
*/
|
|
1848
2032
|
async lookupTransferByPid(pid) {
|
|
1849
2033
|
Guards.stringValue(DataspaceControlPlaneService.CLASS_NAME, "pid", pid);
|
|
1850
|
-
//
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
2034
|
+
// consumerPid is the primary key on BOTH nodes, so the matched key alone is not a reliable role
|
|
2035
|
+
// signal; locate the record by primary, then the providerPid secondary index.
|
|
2036
|
+
let storageEntity = await this._transferProcessStorage.get(pid);
|
|
2037
|
+
let matchedByConsumerPid = true;
|
|
2038
|
+
if (!storageEntity) {
|
|
2039
|
+
storageEntity = await this._transferProcessStorage.get(pid, "providerPid");
|
|
2040
|
+
matchedByConsumerPid = false;
|
|
1857
2041
|
}
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
entity: this.storageEntityToModel(providerPidEntity),
|
|
1863
|
-
role: TransferProcessRole.Provider
|
|
1864
|
-
};
|
|
2042
|
+
if (!storageEntity) {
|
|
2043
|
+
throw new NotFoundError(DataspaceControlPlaneService.CLASS_NAME, "transferProcessNotFound", pid, {
|
|
2044
|
+
pid
|
|
2045
|
+
});
|
|
1865
2046
|
}
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
2047
|
+
// Prefer the role persisted at write time (set in prepareTransfer / requestTransfer). Fall back
|
|
2048
|
+
// to the matched-key heuristic only for legacy records written before localRole existed.
|
|
2049
|
+
const role = storageEntity.localRole ??
|
|
2050
|
+
(matchedByConsumerPid ? TransferProcessRole.Consumer : TransferProcessRole.Provider);
|
|
2051
|
+
return {
|
|
2052
|
+
entity: this.storageEntityToModel(storageEntity),
|
|
2053
|
+
role
|
|
2054
|
+
};
|
|
1869
2055
|
}
|
|
1870
2056
|
/**
|
|
1871
2057
|
* Get raw policy entries from a catalog dataset.
|