@twin.org/dataspace-control-plane-service 0.0.3-next.49 → 0.0.3-next.51

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.
@@ -10,6 +10,7 @@ import { ComparisonOperator } from "@twin.org/entity";
10
10
  import { EntityStorageConnectorFactory } from "@twin.org/entity-storage-models";
11
11
  import { OdrlPolicyHelper, PolicyRequesterFactory } from "@twin.org/rights-management-models";
12
12
  import { DataspaceProtocolContexts, DataspaceProtocolContractNegotiationTypes, DataspaceProtocolEndpointType, DataspaceProtocolHelper, DataspaceProtocolTransferProcessStateType, DataspaceProtocolTransferProcessTypes } from "@twin.org/standards-dataspace-protocol";
13
+ import { OdrlPolicyType } from "@twin.org/standards-w3c-odrl";
13
14
  import { MetricHelper } from "@twin.org/telemetry-models";
14
15
  import { TrustHelper } from "@twin.org/trust-models";
15
16
  import { DataspaceControlPlanePolicyRequester } from "./dataspaceControlPlanePolicyRequester.js";
@@ -174,7 +175,7 @@ export class DataspaceControlPlaneService {
174
175
  ? StringHelper.trimLeadingAndTrailingSlashes(options.config.callbackPath)
175
176
  : undefined;
176
177
  this._taskScheduler = ComponentFactory.getIfExists(options?.taskSchedulerComponentType ?? "task-scheduler");
177
- this._platformComponent = ComponentFactory.getIfExists(options?.platformComponentType ?? "platform");
178
+ this._platformComponent = ComponentFactory.get(options?.platformComponentType ?? "platform");
178
179
  this._telemetryComponent = ComponentFactory.getIfExists(options?.telemetryComponentType);
179
180
  // Data plane component is optional and resolved lazily. The control plane is initialised
180
181
  // BEFORE the data plane in engine startup order, so `getRegisteredInstanceTypeOptional`
@@ -265,7 +266,7 @@ export class DataspaceControlPlaneService {
265
266
  let totalDatasets = 0;
266
267
  // The platform component execute is used so that the dataset publication runs in the
267
268
  // tenant context, also works in single tenant mode
268
- await this._platformComponent?.execute(async () => {
269
+ await this._platformComponent.execute(async () => {
269
270
  // The tenant context id is set here for each system tenant
270
271
  let cursor;
271
272
  do {
@@ -1295,7 +1296,7 @@ export class DataspaceControlPlaneService {
1295
1296
  * @param offerId The offer ID from the provider's catalog.
1296
1297
  * @param providerEndpoint The provider's contract negotiation endpoint URL.
1297
1298
  * @param trustPayload The trust payload for authentication.
1298
- * @returns The negotiation ID. Use the registered callback for completion notification.
1299
+ * @returns For implicit trust: `{ agreementId }`. For external negotiation: `{ negotiationId }`.
1299
1300
  */
1300
1301
  async negotiateAgreement(datasetId, offerId, providerEndpoint, trustPayload) {
1301
1302
  Guards.stringValue(DataspaceControlPlaneService.CLASS_NAME, "datasetId", datasetId);
@@ -1375,6 +1376,9 @@ export class DataspaceControlPlaneService {
1375
1376
  offerType: getJsonLdType(matchingOffer)
1376
1377
  }
1377
1378
  });
1379
+ if (trustInfo.identity === organizationId) {
1380
+ return this.negotiateImplicitTrustAgreement(organizationId, datasetId);
1381
+ }
1378
1382
  const negotiationId = await this._policyNegotiationPointComponent.sendRequestToProvider(providerEndpoint, DataspaceControlPlaneService._REQUESTER_TYPE, offerId, publicOrigin);
1379
1383
  if (!negotiationId) {
1380
1384
  throw new GeneralError(DataspaceControlPlaneService.CLASS_NAME, "negotiationInitiationFailed", {
@@ -1866,6 +1870,29 @@ export class DataspaceControlPlaneService {
1866
1870
  });
1867
1871
  }
1868
1872
  }
1873
+ /**
1874
+ * Resolve the control-plane component for the given URL and invoke an action with it. When the
1875
+ * URL maps to a local origin the action runs against this instance inside that origin's context
1876
+ * (avoiding HTTP serialisation). Otherwise the action runs against a remote REST client. Falls
1877
+ * back to remote when no platform component is available or the locality check throws.
1878
+ * @param url The endpoint URL to resolve.
1879
+ * @param action The action to run with the resolved component.
1880
+ * @returns The result of the action.
1881
+ * @internal
1882
+ */
1883
+ async withControlPlaneComponent(url, action) {
1884
+ try {
1885
+ const localContext = await this._platformComponent.getLocalOriginContext(url);
1886
+ if (!Is.empty(localContext)) {
1887
+ return await ContextIdStore.run(localContext, async () => action(this));
1888
+ }
1889
+ }
1890
+ catch {
1891
+ // Fall back to remote component if locality check throws
1892
+ }
1893
+ const remoteComponent = ComponentFactory.create(this._remoteControlPlaneComponentType, { endpoint: url, pathPrefix: "" });
1894
+ return action(remoteComponent);
1895
+ }
1869
1896
  /**
1870
1897
  * Deliver a transfer state-change DSP message to the consumer's callback (provider → consumer), via a
1871
1898
  * remote control-plane client and the supplied sender. Mirrors negotiation's sendOfferToConsumer.
@@ -1891,8 +1918,7 @@ export class DataspaceControlPlaneService {
1891
1918
  agreementId: entity.agreementId
1892
1919
  }
1893
1920
  });
1894
- const remoteControlPlane = ComponentFactory.create(this._remoteControlPlaneComponentType, { endpoint: entity.callbackAddress, pathPrefix: "" });
1895
- const result = await send(remoteControlPlane, outboundToken);
1921
+ const result = await this.withControlPlaneComponent(entity.callbackAddress, async (component) => send(component, outboundToken));
1896
1922
  if (getJsonLdType(result) === DataspaceProtocolTransferProcessTypes.TransferError) {
1897
1923
  await this._loggingComponent?.log({
1898
1924
  level: "error",
@@ -2488,5 +2514,67 @@ export class DataspaceControlPlaneService {
2488
2514
  const contextIds = await ContextIdStore.getContextIds();
2489
2515
  return contextIds?.[ContextIdKeys.Tenant];
2490
2516
  }
2517
+ /**
2518
+ * Return an existing full-access agreement for the same-organization (implicit trust) case,
2519
+ * or create and store one if none exists. Fires onFinalized on all registered callbacks in
2520
+ * both cases.
2521
+ * @param organizationId The local organization ID (both assigner and assignee).
2522
+ * @param datasetId The dataset being granted access to.
2523
+ * @returns The agreement ID.
2524
+ * @internal
2525
+ */
2526
+ async negotiateImplicitTrustAgreement(organizationId, datasetId) {
2527
+ const { policies } = await this._policyAdministrationPointComponent.query({
2528
+ type: OdrlPolicyType.Agreement,
2529
+ assigner: organizationId,
2530
+ assignee: organizationId,
2531
+ target: datasetId
2532
+ });
2533
+ let agreementId;
2534
+ if (policies.length > 0) {
2535
+ agreementId = OdrlPolicyHelper.getUid(policies[0]);
2536
+ await this._loggingComponent?.log({
2537
+ level: "info",
2538
+ source: DataspaceControlPlaneService.CLASS_NAME,
2539
+ ts: Date.now(),
2540
+ message: "implicitTrustAgreementReused",
2541
+ data: { agreementId, datasetId, organizationId }
2542
+ });
2543
+ }
2544
+ else {
2545
+ const implicitAgreement = {
2546
+ "@context": "http://www.w3.org/ns/odrl.jsonld",
2547
+ "@type": "Agreement",
2548
+ assigner: organizationId,
2549
+ assignee: organizationId,
2550
+ target: datasetId,
2551
+ permission: [{ action: "use" }]
2552
+ };
2553
+ agreementId = await this._policyAdministrationPointComponent.create(implicitAgreement);
2554
+ await MetricHelper.metricIncrement(this._telemetryComponent, DataspaceControlPlaneMetricIds.NegotiationsInitiated);
2555
+ await this._loggingComponent?.log({
2556
+ level: "info",
2557
+ source: DataspaceControlPlaneService.CLASS_NAME,
2558
+ ts: Date.now(),
2559
+ message: "implicitTrustAgreementCreated",
2560
+ data: { agreementId, datasetId, organizationId }
2561
+ });
2562
+ }
2563
+ for (const [key, cb] of this._negotiationCallbacks.entries()) {
2564
+ try {
2565
+ await cb.onFinalized(undefined, agreementId);
2566
+ }
2567
+ catch (error) {
2568
+ await this._loggingComponent?.log({
2569
+ level: "error",
2570
+ source: DataspaceControlPlaneService.CLASS_NAME,
2571
+ ts: Date.now(),
2572
+ message: "negotiationCallbackError",
2573
+ data: { key, negotiationId: agreementId, method: "onFinalized", error }
2574
+ });
2575
+ }
2576
+ }
2577
+ return { agreementId };
2578
+ }
2491
2579
  }
2492
2580
  //# sourceMappingURL=dataspaceControlPlaneService.js.map