@twin.org/rights-management-plugins 0.0.3-next.29 → 0.0.3-next.31

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.
Files changed (25) hide show
  1. package/dist/es/index.js +5 -2
  2. package/dist/es/index.js.map +1 -1
  3. package/dist/es/models/IAutomationPolicyExecutionActionConfig.js +4 -0
  4. package/dist/es/models/IAutomationPolicyExecutionActionConfig.js.map +1 -0
  5. package/dist/es/models/IAutomationPolicyExecutionActionConstructorOptions.js +2 -0
  6. package/dist/es/models/IAutomationPolicyExecutionActionConstructorOptions.js.map +1 -0
  7. package/dist/es/policyArbiters/defaultPolicyArbiter.js +270 -90
  8. package/dist/es/policyArbiters/defaultPolicyArbiter.js.map +1 -1
  9. package/dist/es/policyExecutionActions/automationPolicyExecutionAction.js +76 -0
  10. package/dist/es/policyExecutionActions/automationPolicyExecutionAction.js.map +1 -0
  11. package/dist/es/policyExecutionActions/loggingPolicyExecutionAction.js.map +1 -1
  12. package/dist/types/index.d.ts +5 -2
  13. package/dist/types/models/IAutomationPolicyExecutionActionConfig.d.ts +9 -0
  14. package/dist/types/models/IAutomationPolicyExecutionActionConstructorOptions.d.ts +15 -0
  15. package/dist/types/policyExecutionActions/automationPolicyExecutionAction.d.ts +49 -0
  16. package/dist/types/policyExecutionActions/loggingPolicyExecutionAction.d.ts +1 -1
  17. package/docs/changelog.md +100 -48
  18. package/docs/examples.md +0 -29
  19. package/docs/reference/classes/AutomationPolicyExecutionAction.md +143 -0
  20. package/docs/reference/classes/LoggingPolicyExecutionAction.md +1 -1
  21. package/docs/reference/index.md +3 -0
  22. package/docs/reference/interfaces/IAutomationPolicyExecutionActionConfig.md +11 -0
  23. package/docs/reference/interfaces/IAutomationPolicyExecutionActionConstructorOptions.md +25 -0
  24. package/locales/en.json +2 -1
  25. package/package.json +3 -2
package/dist/es/index.js CHANGED
@@ -1,7 +1,9 @@
1
1
  // Copyright 2025 IOTA Stiftung.
2
2
  // SPDX-License-Identifier: Apache-2.0.
3
- export * from "./models/IDefaultPolicyArbiterConstructorOptions.js";
3
+ export * from "./models/IAutomationPolicyExecutionActionConfig.js";
4
+ export * from "./models/IAutomationPolicyExecutionActionConstructorOptions.js";
4
5
  export * from "./models/IDefaultPolicyArbiterConfig.js";
6
+ export * from "./models/IDefaultPolicyArbiterConstructorOptions.js";
5
7
  export * from "./models/IDefaultPolicyEnforcementProcessorConstructorOptions.js";
6
8
  export * from "./models/IIdentityPolicyInformationSourceConstructorOptions.js";
7
9
  export * from "./models/ILoggingPolicyExecutionActionConfig.js";
@@ -18,10 +20,11 @@ export * from "./policyArbiters/defaultPolicyArbiter.js";
18
20
  export * from "./policyArbiters/passThroughPolicyArbiter.js";
19
21
  export * from "./policyEnforcementProcessor/defaultPolicyEnforcementProcessor.js";
20
22
  export * from "./policyEnforcementProcessor/passThroughPolicyEnforcementProcessor.js";
23
+ export * from "./policyExecutionActions/automationPolicyExecutionAction.js";
21
24
  export * from "./policyExecutionActions/loggingPolicyExecutionAction.js";
22
- export * from "./policyObligationEnforcers/passThroughPolicyObligationEnforcer.js";
23
25
  export * from "./policyInformationSources/identityPolicyInformationSource.js";
24
26
  export * from "./policyInformationSources/staticPolicyInformationSource.js";
25
27
  export * from "./policyNegotiators/passThroughPolicyNegotiator.js";
28
+ export * from "./policyObligationEnforcers/passThroughPolicyObligationEnforcer.js";
26
29
  export * from "./policyRequesters/passThroughPolicyRequester.js";
27
30
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,cAAc,qDAAqD,CAAC;AACpE,cAAc,yCAAyC,CAAC;AACxD,cAAc,kEAAkE,CAAC;AACjF,cAAc,gEAAgE,CAAC;AAC/E,cAAc,iDAAiD,CAAC;AAChE,cAAc,6DAA6D,CAAC;AAC5E,cAAc,yDAAyD,CAAC;AACxE,cAAc,sEAAsE,CAAC;AACrF,cAAc,4DAA4D,CAAC;AAC3E,cAAc,oEAAoE,CAAC;AACnF,cAAc,2DAA2D,CAAC;AAC1E,cAAc,4CAA4C,CAAC;AAC3D,cAAc,kDAAkD,CAAC;AACjE,cAAc,8DAA8D,CAAC;AAC7E,cAAc,0CAA0C,CAAC;AACzD,cAAc,8CAA8C,CAAC;AAC7D,cAAc,mEAAmE,CAAC;AAClF,cAAc,uEAAuE,CAAC;AACtF,cAAc,0DAA0D,CAAC;AACzE,cAAc,oEAAoE,CAAC;AACnF,cAAc,+DAA+D,CAAC;AAC9E,cAAc,6DAA6D,CAAC;AAC5E,cAAc,oDAAoD,CAAC;AACnE,cAAc,kDAAkD,CAAC","sourcesContent":["// Copyright 2025 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nexport * from \"./models/IDefaultPolicyArbiterConstructorOptions.js\";\nexport * from \"./models/IDefaultPolicyArbiterConfig.js\";\nexport * from \"./models/IDefaultPolicyEnforcementProcessorConstructorOptions.js\";\nexport * from \"./models/IIdentityPolicyInformationSourceConstructorOptions.js\";\nexport * from \"./models/ILoggingPolicyExecutionActionConfig.js\";\nexport * from \"./models/ILoggingPolicyExecutionActionConstructorOptions.js\";\nexport * from \"./models/IPassThroughPolicyArbiterConstructorOptions.js\";\nexport * from \"./models/IPassThroughPolicyEnforcementProcessorConstructorOptions.js\";\nexport * from \"./models/IPassThroughPolicyNegotiatorConstructorOptions.js\";\nexport * from \"./models/IPassThroughPolicyObligationEnforcerConstructorOptions.js\";\nexport * from \"./models/IPassThroughPolicyRequesterConstructorOptions.js\";\nexport * from \"./models/IStaticPolicyInformationSource.js\";\nexport * from \"./models/IStaticPolicyInformationSourceConfig.js\";\nexport * from \"./models/IStaticPolicyInformationSourceConstructorOptions.js\";\nexport * from \"./policyArbiters/defaultPolicyArbiter.js\";\nexport * from \"./policyArbiters/passThroughPolicyArbiter.js\";\nexport * from \"./policyEnforcementProcessor/defaultPolicyEnforcementProcessor.js\";\nexport * from \"./policyEnforcementProcessor/passThroughPolicyEnforcementProcessor.js\";\nexport * from \"./policyExecutionActions/loggingPolicyExecutionAction.js\";\nexport * from \"./policyObligationEnforcers/passThroughPolicyObligationEnforcer.js\";\nexport * from \"./policyInformationSources/identityPolicyInformationSource.js\";\nexport * from \"./policyInformationSources/staticPolicyInformationSource.js\";\nexport * from \"./policyNegotiators/passThroughPolicyNegotiator.js\";\nexport * from \"./policyRequesters/passThroughPolicyRequester.js\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,cAAc,oDAAoD,CAAC;AACnE,cAAc,gEAAgE,CAAC;AAC/E,cAAc,yCAAyC,CAAC;AACxD,cAAc,qDAAqD,CAAC;AACpE,cAAc,kEAAkE,CAAC;AACjF,cAAc,gEAAgE,CAAC;AAC/E,cAAc,iDAAiD,CAAC;AAChE,cAAc,6DAA6D,CAAC;AAC5E,cAAc,yDAAyD,CAAC;AACxE,cAAc,sEAAsE,CAAC;AACrF,cAAc,4DAA4D,CAAC;AAC3E,cAAc,oEAAoE,CAAC;AACnF,cAAc,2DAA2D,CAAC;AAC1E,cAAc,4CAA4C,CAAC;AAC3D,cAAc,kDAAkD,CAAC;AACjE,cAAc,8DAA8D,CAAC;AAC7E,cAAc,0CAA0C,CAAC;AACzD,cAAc,8CAA8C,CAAC;AAC7D,cAAc,mEAAmE,CAAC;AAClF,cAAc,uEAAuE,CAAC;AACtF,cAAc,6DAA6D,CAAC;AAC5E,cAAc,0DAA0D,CAAC;AACzE,cAAc,+DAA+D,CAAC;AAC9E,cAAc,6DAA6D,CAAC;AAC5E,cAAc,oDAAoD,CAAC;AACnE,cAAc,oEAAoE,CAAC;AACnF,cAAc,kDAAkD,CAAC","sourcesContent":["// Copyright 2025 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nexport * from \"./models/IAutomationPolicyExecutionActionConfig.js\";\nexport * from \"./models/IAutomationPolicyExecutionActionConstructorOptions.js\";\nexport * from \"./models/IDefaultPolicyArbiterConfig.js\";\nexport * from \"./models/IDefaultPolicyArbiterConstructorOptions.js\";\nexport * from \"./models/IDefaultPolicyEnforcementProcessorConstructorOptions.js\";\nexport * from \"./models/IIdentityPolicyInformationSourceConstructorOptions.js\";\nexport * from \"./models/ILoggingPolicyExecutionActionConfig.js\";\nexport * from \"./models/ILoggingPolicyExecutionActionConstructorOptions.js\";\nexport * from \"./models/IPassThroughPolicyArbiterConstructorOptions.js\";\nexport * from \"./models/IPassThroughPolicyEnforcementProcessorConstructorOptions.js\";\nexport * from \"./models/IPassThroughPolicyNegotiatorConstructorOptions.js\";\nexport * from \"./models/IPassThroughPolicyObligationEnforcerConstructorOptions.js\";\nexport * from \"./models/IPassThroughPolicyRequesterConstructorOptions.js\";\nexport * from \"./models/IStaticPolicyInformationSource.js\";\nexport * from \"./models/IStaticPolicyInformationSourceConfig.js\";\nexport * from \"./models/IStaticPolicyInformationSourceConstructorOptions.js\";\nexport * from \"./policyArbiters/defaultPolicyArbiter.js\";\nexport * from \"./policyArbiters/passThroughPolicyArbiter.js\";\nexport * from \"./policyEnforcementProcessor/defaultPolicyEnforcementProcessor.js\";\nexport * from \"./policyEnforcementProcessor/passThroughPolicyEnforcementProcessor.js\";\nexport * from \"./policyExecutionActions/automationPolicyExecutionAction.js\";\nexport * from \"./policyExecutionActions/loggingPolicyExecutionAction.js\";\nexport * from \"./policyInformationSources/identityPolicyInformationSource.js\";\nexport * from \"./policyInformationSources/staticPolicyInformationSource.js\";\nexport * from \"./policyNegotiators/passThroughPolicyNegotiator.js\";\nexport * from \"./policyObligationEnforcers/passThroughPolicyObligationEnforcer.js\";\nexport * from \"./policyRequesters/passThroughPolicyRequester.js\";\n"]}
@@ -0,0 +1,4 @@
1
+ // Copyright 2026 IOTA Stiftung.
2
+ // SPDX-License-Identifier: Apache-2.0.
3
+ export {};
4
+ //# sourceMappingURL=IAutomationPolicyExecutionActionConfig.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IAutomationPolicyExecutionActionConfig.js","sourceRoot":"","sources":["../../../src/models/IAutomationPolicyExecutionActionConfig.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\n\n/**\n * Options for the Automation Policy Execution Action Component.\n */\nexport interface IAutomationPolicyExecutionActionConfig {\n\t/**\n\t * The policy decision stages to trigger the automation actions, if undefined defaults to \"inform\".\n\t */\n\ttriggerActions?: string[];\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=IAutomationPolicyExecutionActionConstructorOptions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IAutomationPolicyExecutionActionConstructorOptions.js","sourceRoot":"","sources":["../../../src/models/IAutomationPolicyExecutionActionConstructorOptions.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IAutomationPolicyExecutionActionConfig } from \"./IAutomationPolicyExecutionActionConfig.js\";\n\n/**\n * Options for the Automation Policy Execution Action.\n */\nexport interface IAutomationPolicyExecutionActionConstructorOptions {\n\t/**\n\t * The automation component for executing automation policies.\n\t * @default automation\n\t */\n\tautomationComponentType?: string;\n\n\t/**\n\t * The configuration for the automation policy execution.\n\t */\n\tconfig?: IAutomationPolicyExecutionActionConfig;\n}\n"]}
@@ -24,20 +24,31 @@ export class DefaultPolicyArbiter {
24
24
  */
25
25
  static _DEFAULT_MAX_INHERITANCE_DEPTH = 10;
26
26
  /**
27
- * TWIN prefix operations.
27
+ * Datasource key for the primary JSON path data source.
28
28
  * @internal
29
29
  */
30
- static _TWIN_PREFIX_OPERATIONS = "twin:";
30
+ static _DATA_SOURCE_KEY = "data";
31
31
  /**
32
- * TWIN prefix JSONPath.
32
+ * Datasource key for the information source used in JSON path expressions.
33
33
  * @internal
34
34
  */
35
- static _TWIN_PREFIX_JSONPATH = "jsonpath";
35
+ static _INFORMATION_SOURCE_KEY = "information";
36
36
  /**
37
- * TWIN prefix information.
37
+ * TWIN prefix JSONPath canonical alias.
38
38
  * @internal
39
39
  */
40
- static _TWIN_PREFIX_INFORMATION = "information";
40
+ static _TWIN_JSONPATH = "twin:jsonPath";
41
+ /**
42
+ * Canonical jsonPath expression property.
43
+ * @internal
44
+ */
45
+ static _TWIN_JSONPATH_EXPRESSION = "twin:jsonPathExpression";
46
+ /**
47
+ * Optional data source key property for canonical twin:jsonPath targets.
48
+ * When absent, defaults to the primary data source.
49
+ * @internal
50
+ */
51
+ static _TWIN_JSONPATH_DATA_SOURCE = "twin:jsonPathDataSource";
41
52
  /**
42
53
  * The logging component.
43
54
  * @internal
@@ -141,8 +152,8 @@ export class DefaultPolicyArbiter {
141
152
  const mergedPolicy = await this.mergeInheritedPolicies(agreement);
142
153
  const expandedPolicy = this.expandCompactPolicyRules(mergedPolicy);
143
154
  const dataSources = {
144
- [`${DefaultPolicyArbiter._TWIN_PREFIX_OPERATIONS}${DefaultPolicyArbiter._TWIN_PREFIX_JSONPATH}`]: data,
145
- [`${DefaultPolicyArbiter._TWIN_PREFIX_OPERATIONS}${DefaultPolicyArbiter._TWIN_PREFIX_INFORMATION}`]: information
155
+ [DefaultPolicyArbiter._DATA_SOURCE_KEY]: data,
156
+ [DefaultPolicyArbiter._INFORMATION_SOURCE_KEY]: information
146
157
  };
147
158
  // Extract agreement parties once for use in rule evaluation
148
159
  const agreementAssigner = OdrlPolicyHelper.getPartyIds(agreement.assigner);
@@ -797,7 +808,7 @@ export class DefaultPolicyArbiter {
797
808
  */
798
809
  async enforceDuty(policy, duty, dataSources, ruleDataContext) {
799
810
  const enforcerNames = PolicyObligationEnforcerFactory.names();
800
- const information = dataSources[`${DefaultPolicyArbiter._TWIN_PREFIX_OPERATIONS}${DefaultPolicyArbiter._TWIN_PREFIX_INFORMATION}`];
811
+ const information = dataSources[DefaultPolicyArbiter._INFORMATION_SOURCE_KEY];
801
812
  if (enforcerNames.length === 0) {
802
813
  throw new GeneralError(DefaultPolicyArbiter.CLASS_NAME, "noObligationEnforcersRegistered");
803
814
  }
@@ -827,51 +838,70 @@ export class DefaultPolicyArbiter {
827
838
  * @internal
828
839
  */
829
840
  tryResolveTargetDataSource(targetId, dataSources, resolveValue = false) {
830
- // If there is no target id, default to the entire "twin:jsonpath" datasource
841
+ // If there is no target id, default to the data datasource
831
842
  if (Is.empty(targetId)) {
832
843
  return {
833
- prefix: `${DefaultPolicyArbiter._TWIN_PREFIX_OPERATIONS}${DefaultPolicyArbiter._TWIN_PREFIX_JSONPATH}`,
834
- source: dataSources[`${DefaultPolicyArbiter._TWIN_PREFIX_OPERATIONS}${DefaultPolicyArbiter._TWIN_PREFIX_JSONPATH}`],
844
+ source: dataSources[DefaultPolicyArbiter._DATA_SOURCE_KEY],
835
845
  target: "$",
836
- value: dataSources[`${DefaultPolicyArbiter._TWIN_PREFIX_OPERATIONS}${DefaultPolicyArbiter._TWIN_PREFIX_JSONPATH}`]
846
+ value: dataSources[DefaultPolicyArbiter._DATA_SOURCE_KEY]
837
847
  };
838
848
  }
839
- // Otherwise lookup the target id prefix in the datasources and return the remaining suffix as the target path/key
840
- const prefixes = Object.keys(dataSources).sort((a, b) => b.length - a.length);
841
- for (const prefix of prefixes) {
842
- if (targetId.startsWith(`${prefix}:`)) {
843
- const source = dataSources[prefix];
844
- const target = targetId.slice(prefix.length + 1);
845
- if (!target.startsWith("$")) {
846
- throw new GeneralError(DefaultPolicyArbiter.CLASS_NAME, "ruleTargetNotSupported", {
847
- target: targetId
848
- });
849
- }
850
- if (!resolveValue) {
851
- return {
852
- prefix,
853
- source,
854
- target
855
- };
856
- }
857
- const matches = JsonPathHelper.query(target, source);
858
- if (matches.length === 0) {
859
- throw new GeneralError(DefaultPolicyArbiter.CLASS_NAME, "ruleTargetNotSupported", {
860
- target: targetId
861
- });
862
- }
863
- return {
864
- prefix,
865
- source,
866
- target,
867
- value: matches.length === 1 ? matches[0].value : matches.map(m => m.value)
868
- };
869
- }
849
+ // Handle twin:jsonPath:<datasource>:<expression> format.
850
+ // The datasource segment is optional; when absent the expression starts with "$"
851
+ // and falls back to the primary data source.
852
+ if (targetId.startsWith(`${DefaultPolicyArbiter._TWIN_JSONPATH}:`)) {
853
+ const rest = targetId.slice(DefaultPolicyArbiter._TWIN_JSONPATH.length + 1);
854
+ const matchingKey = Object.keys(dataSources).find(k => rest.startsWith(`${k}:`));
855
+ const sourceKey = matchingKey ?? DefaultPolicyArbiter._DATA_SOURCE_KEY;
856
+ const expression = matchingKey ? rest.slice(matchingKey.length + 1) : rest;
857
+ return this.resolveDataSourceByKey(sourceKey, expression, dataSources, resolveValue);
858
+ }
859
+ // Handle <datasource>:<expression> format
860
+ const matchingKey = Object.keys(dataSources).find(k => targetId.startsWith(`${k}:`));
861
+ if (matchingKey) {
862
+ const expression = targetId.slice(matchingKey.length + 1);
863
+ return this.resolveDataSourceByKey(matchingKey, expression, dataSources, resolveValue);
870
864
  }
871
865
  throw new GeneralError(DefaultPolicyArbiter.CLASS_NAME, "ruleTargetNotSupported", {
872
866
  target: targetId
873
867
  });
874
868
  }
869
+ /**
870
+ * Resolve a datasource by key and evaluate a JSONPath expression against it.
871
+ * @param sourceKey The datasource key.
872
+ * @param expression The JSONPath expression.
873
+ * @param dataSources The available datasources.
874
+ * @param resolveValue Whether to evaluate the expression and return the matched value.
875
+ * @returns The resolved source, target expression, and optionally the matched value.
876
+ * @internal
877
+ */
878
+ resolveDataSourceByKey(sourceKey, expression, dataSources, resolveValue = false) {
879
+ if (!Is.stringValue(expression)) {
880
+ throw new GeneralError(DefaultPolicyArbiter.CLASS_NAME, "jsonPathExpressionMissing", {
881
+ operand: "target"
882
+ });
883
+ }
884
+ if (!expression.startsWith("$")) {
885
+ throw new GeneralError(DefaultPolicyArbiter.CLASS_NAME, "ruleTargetNotSupported", {
886
+ target: `${sourceKey}:${expression}`
887
+ });
888
+ }
889
+ const source = dataSources[sourceKey];
890
+ if (!resolveValue) {
891
+ return { source, target: expression };
892
+ }
893
+ const matches = JsonPathHelper.query(expression, source);
894
+ if (matches.length === 0) {
895
+ throw new GeneralError(DefaultPolicyArbiter.CLASS_NAME, "ruleTargetNotSupported", {
896
+ target: `${sourceKey}:${expression}`
897
+ });
898
+ }
899
+ return {
900
+ source,
901
+ target: expression,
902
+ value: matches.length === 1 ? matches[0].value : matches.map(m => m.value)
903
+ };
904
+ }
875
905
  /**
876
906
  * Extract the target id from the permission.
877
907
  * @param target The permission target.
@@ -889,7 +919,23 @@ export class DefaultPolicyArbiter {
889
919
  if (arr.length === 0) {
890
920
  return undefined;
891
921
  }
892
- return Is.string(arr[0]) ? arr[0] : OdrlPolicyHelper.getUid(arr[0]);
922
+ const first = arr[0];
923
+ if (Is.string(first)) {
924
+ return first;
925
+ }
926
+ if (this.isTwinJsonPathTarget(first)) {
927
+ const t = first;
928
+ const expression = t[DefaultPolicyArbiter._TWIN_JSONPATH_EXPRESSION];
929
+ if (!Is.stringValue(expression)) {
930
+ return undefined;
931
+ }
932
+ const dataSource = t[DefaultPolicyArbiter._TWIN_JSONPATH_DATA_SOURCE];
933
+ const sourceKey = Is.stringValue(dataSource)
934
+ ? dataSource
935
+ : DefaultPolicyArbiter._DATA_SOURCE_KEY;
936
+ return `${sourceKey}:${expression}`;
937
+ }
938
+ return OdrlPolicyHelper.getUid(first);
893
939
  }
894
940
  /**
895
941
  * Resolve the target identifier used for rule data context lookup.
@@ -905,8 +951,16 @@ export class DefaultPolicyArbiter {
905
951
  const firstTarget = arr[0];
906
952
  if (Is.object(firstTarget) &&
907
953
  OdrlPolicyHelper.getType(firstTarget) === OdrlTypes.AssetCollection &&
908
- Is.stringValue(firstTarget.source)) {
909
- return firstTarget.source;
954
+ firstTarget.source === DefaultPolicyArbiter._TWIN_JSONPATH) {
955
+ const ctx = firstTarget;
956
+ const expression = ctx[DefaultPolicyArbiter._TWIN_JSONPATH_EXPRESSION];
957
+ if (Is.stringValue(expression)) {
958
+ const dataSource = ctx[DefaultPolicyArbiter._TWIN_JSONPATH_DATA_SOURCE];
959
+ const sourceKey = Is.stringValue(dataSource)
960
+ ? dataSource
961
+ : DefaultPolicyArbiter._DATA_SOURCE_KEY;
962
+ return `${DefaultPolicyArbiter._TWIN_JSONPATH}:${sourceKey}:${expression}`;
963
+ }
910
964
  }
911
965
  }
912
966
  return this.getTargetId(target);
@@ -952,6 +1006,14 @@ export class DefaultPolicyArbiter {
952
1006
  throw new GeneralError(DefaultPolicyArbiter.CLASS_NAME, "multipleTargetsNotSupported");
953
1007
  }
954
1008
  const firstTarget = arr[0];
1009
+ if (this.isTwinJsonPathTarget(firstTarget)) {
1010
+ const t = firstTarget;
1011
+ const resolved = this.resolveDataSourceByKey(t[DefaultPolicyArbiter._TWIN_JSONPATH_DATA_SOURCE] ?? DefaultPolicyArbiter._DATA_SOURCE_KEY, t[DefaultPolicyArbiter._TWIN_JSONPATH_EXPRESSION], dataSources);
1012
+ return {
1013
+ target: resolved.target,
1014
+ refinements: []
1015
+ };
1016
+ }
955
1017
  if (Is.object(firstTarget)) {
956
1018
  // Guard against unsupported ODRL asset properties
957
1019
  if (Is.notEmpty(firstTarget.hasPolicy)) {
@@ -962,18 +1024,23 @@ export class DefaultPolicyArbiter {
962
1024
  }
963
1025
  if (Is.object(firstTarget) &&
964
1026
  OdrlPolicyHelper.getType(firstTarget) === OdrlTypes.AssetCollection) {
965
- if (!Is.stringValue(firstTarget.source)) {
1027
+ if (firstTarget.source !== DefaultPolicyArbiter._TWIN_JSONPATH) {
966
1028
  throw new GeneralError(DefaultPolicyArbiter.CLASS_NAME, "assetCollectionSourceNotSupported", {
967
1029
  source: firstTarget.source ?? ""
968
1030
  });
969
1031
  }
1032
+ const ctx = firstTarget;
1033
+ const dataSource = ctx[DefaultPolicyArbiter._TWIN_JSONPATH_DATA_SOURCE];
1034
+ const sourceKey = Is.stringValue(dataSource)
1035
+ ? dataSource
1036
+ : DefaultPolicyArbiter._DATA_SOURCE_KEY;
970
1037
  let sourceLookup;
971
1038
  try {
972
- sourceLookup = this.tryResolveTargetDataSource(firstTarget.source, dataSources);
1039
+ sourceLookup = this.resolveDataSourceByKey(sourceKey, ctx[DefaultPolicyArbiter._TWIN_JSONPATH_EXPRESSION], dataSources);
973
1040
  }
974
1041
  catch {
975
1042
  throw new GeneralError(DefaultPolicyArbiter.CLASS_NAME, "assetCollectionSourceNotSupported", {
976
- source: firstTarget.source
1043
+ source: ctx[DefaultPolicyArbiter._TWIN_JSONPATH_EXPRESSION] ?? ""
977
1044
  });
978
1045
  }
979
1046
  return {
@@ -1054,11 +1121,18 @@ export class DefaultPolicyArbiter {
1054
1121
  };
1055
1122
  }
1056
1123
  const regularConstraint = refinement;
1057
- return {
1124
+ const constraintWithExtensions = regularConstraint;
1125
+ const canonicalExpression = constraintWithExtensions[DefaultPolicyArbiter._TWIN_JSONPATH_EXPRESSION];
1126
+ const rewrittenConstraint = {
1058
1127
  ...regularConstraint,
1059
1128
  leftOperand: this.rewriteOperandForDecisionTarget(regularConstraint.leftOperand, sourceTarget, itemTarget),
1060
1129
  rightOperand: this.rewriteOperandForDecisionTarget(regularConstraint.rightOperand, sourceTarget, itemTarget)
1061
1130
  };
1131
+ if (Is.stringValue(canonicalExpression)) {
1132
+ rewrittenConstraint[DefaultPolicyArbiter._TWIN_JSONPATH_EXPRESSION] =
1133
+ this.rewriteWildcardPath(canonicalExpression, sourceTarget, itemTarget);
1134
+ }
1135
+ return rewrittenConstraint;
1062
1136
  }
1063
1137
  /**
1064
1138
  * Rewrite JSONPath-based operands from wildcard source to concrete item target.
@@ -1069,23 +1143,33 @@ export class DefaultPolicyArbiter {
1069
1143
  * @internal
1070
1144
  */
1071
1145
  rewriteOperandForDecisionTarget(operand, sourceTarget, itemTarget) {
1072
- if (Is.stringValue(operand)) {
1073
- const prefix = `${DefaultPolicyArbiter._TWIN_PREFIX_OPERATIONS}${DefaultPolicyArbiter._TWIN_PREFIX_JSONPATH}:`;
1074
- if (operand.startsWith(prefix)) {
1075
- const valuePath = operand.slice(prefix.length);
1076
- return `${prefix}${this.rewriteWildcardPath(valuePath, sourceTarget, itemTarget)}`;
1077
- }
1078
- return operand;
1079
- }
1080
1146
  if (Is.object(operand)) {
1081
- const typedOperand = { ...operand };
1082
- if (typedOperand["@type"] ===
1083
- `${DefaultPolicyArbiter._TWIN_PREFIX_OPERATIONS}${DefaultPolicyArbiter._TWIN_PREFIX_JSONPATH}` &&
1084
- Is.stringValue(typedOperand["@value"])) {
1085
- typedOperand["@value"] = this.rewriteWildcardPath(typedOperand["@value"], sourceTarget, itemTarget);
1147
+ const typedOperand = {
1148
+ ...operand
1149
+ };
1150
+ if (this.isTwinJsonPathOperandType(typedOperand["@type"])) {
1151
+ if (Is.stringValue(typedOperand["@value"])) {
1152
+ typedOperand["@value"] = this.rewriteWildcardPath(typedOperand["@value"], sourceTarget, itemTarget);
1153
+ }
1154
+ const canonicalExpression = typedOperand[DefaultPolicyArbiter._TWIN_JSONPATH_EXPRESSION];
1155
+ if (Is.stringValue(canonicalExpression)) {
1156
+ typedOperand[DefaultPolicyArbiter._TWIN_JSONPATH_EXPRESSION] = this.rewriteWildcardPath(canonicalExpression, sourceTarget, itemTarget);
1157
+ }
1086
1158
  }
1087
1159
  return typedOperand;
1088
1160
  }
1161
+ // Legacy string form: "twin:information:$.foo". Extract the prefix, rewrite the
1162
+ // JSONPath expression to scope to the per-item target, then re-assemble. Without
1163
+ // this, the leftOperand stays at the wildcard
1164
+ // `$.itemList.itemListElement[*].unloadingLocation.id` for every item, returning
1165
+ // the full array of all ids per check — so all items pass the equality test.
1166
+ if (Is.stringValue(operand) &&
1167
+ operand.startsWith(`twin:${DefaultPolicyArbiter._INFORMATION_SOURCE_KEY}:`)) {
1168
+ const prefix = `twin:${DefaultPolicyArbiter._INFORMATION_SOURCE_KEY}:`;
1169
+ const expression = operand.slice(prefix.length);
1170
+ const rewritten = this.rewriteWildcardPath(expression, sourceTarget, itemTarget);
1171
+ return `${prefix}${rewritten}`;
1172
+ }
1089
1173
  return operand;
1090
1174
  }
1091
1175
  /**
@@ -1150,8 +1234,8 @@ export class DefaultPolicyArbiter {
1150
1234
  throw new GeneralError(DefaultPolicyArbiter.CLASS_NAME, "constraintStatusNotSupported");
1151
1235
  }
1152
1236
  // Evaluate the main constraint condition
1153
- const leftValue = this.calculateOperandValue(regularConstraint.leftOperand, dataSources);
1154
- const rightValue = this.calculateOperandValue(regularConstraint.rightOperand, dataSources);
1237
+ const leftValue = this.calculateOperandValue(regularConstraint.leftOperand, dataSources, regularConstraint);
1238
+ const rightValue = this.calculateOperandValue(regularConstraint.rightOperand, dataSources, regularConstraint);
1155
1239
  const mainSatisfied = this.evaluateOperator(regularConstraint.operator, leftValue, rightValue);
1156
1240
  // If main constraint is not satisfied, the overall constraint fails
1157
1241
  return mainSatisfied;
@@ -1276,27 +1360,24 @@ export class DefaultPolicyArbiter {
1276
1360
  * @internal
1277
1361
  */
1278
1362
  tryResolveOperandLookup(operandTypeOrValue, operandValue, dataSources) {
1279
- let lookupTargetId;
1280
- if (dataSources[operandTypeOrValue]) {
1281
- if (!Is.stringValue(operandValue)) {
1282
- return undefined;
1363
+ let sourceKey = this.normalizeTwinJsonPathOperandAlias(operandTypeOrValue);
1364
+ let value = operandValue;
1365
+ // Combined form: operandTypeOrValue is a full "<prefix>:<expression>" string
1366
+ // like "twin:jsonPath:$.foo" rather than the canonical separation of @type +
1367
+ // @value/expression. Detect by walking the registered datasource keys for a prefix
1368
+ // match; on hit, split into key + expression so resolveDataSourceByKey can evaluate.
1369
+ if (!dataSources[sourceKey]) {
1370
+ const matchingKey = Object.keys(dataSources).find(k => sourceKey.startsWith(`${k}:`));
1371
+ if (matchingKey) {
1372
+ value = sourceKey.slice(matchingKey.length + 1);
1373
+ sourceKey = matchingKey;
1283
1374
  }
1284
- lookupTargetId = `${operandTypeOrValue}:${operandValue}`;
1285
- }
1286
- else if (operandTypeOrValue.includes(":")) {
1287
- lookupTargetId = operandTypeOrValue;
1288
1375
  }
1289
- else {
1376
+ if (!dataSources[sourceKey] || !Is.stringValue(value)) {
1290
1377
  return undefined;
1291
1378
  }
1292
- // Delegate prefixed path matching to shared datasource resolver
1293
- if (operandTypeOrValue.startsWith(DefaultPolicyArbiter._TWIN_PREFIX_OPERATIONS)) {
1294
- const resolved = this.tryResolveTargetDataSource(lookupTargetId, dataSources);
1295
- return {
1296
- source: resolved.source,
1297
- jsonPath: resolved.target
1298
- };
1299
- }
1379
+ const resolved = this.resolveDataSourceByKey(sourceKey, value, dataSources);
1380
+ return { source: resolved.source, jsonPath: resolved.target };
1300
1381
  }
1301
1382
  /**
1302
1383
  * Calculate an operand value.
@@ -1305,24 +1386,40 @@ export class DefaultPolicyArbiter {
1305
1386
  * @returns The resolved operand value.
1306
1387
  * @internal
1307
1388
  */
1308
- calculateOperandValue(operand, dataSources) {
1309
- // Treat prefixed operands as selectors against a namespaced source dictionary.
1310
- // Examples: twin:jsonpath:$.field, twin:information:$.credentials.level
1389
+ calculateOperandValue(operand, dataSources, constraint) {
1311
1390
  let jsonPath;
1312
1391
  let operandRoot;
1313
1392
  if (Is.stringValue(operand)) {
1314
- const lookup = this.tryResolveOperandLookup(operand, operand, dataSources);
1393
+ const expression = this.extractJsonPathExpressionFromConstraint(constraint, operand);
1394
+ let resolvedOperand = operand;
1395
+ if (operand === DefaultPolicyArbiter._TWIN_JSONPATH &&
1396
+ Is.object(constraint)) {
1397
+ const dataSourceOverride = constraint[DefaultPolicyArbiter._TWIN_JSONPATH_DATA_SOURCE];
1398
+ if (Is.stringValue(dataSourceOverride)) {
1399
+ resolvedOperand = dataSourceOverride;
1400
+ }
1401
+ }
1402
+ const lookup = this.tryResolveOperandLookup(resolvedOperand, expression ?? operand, dataSources);
1315
1403
  if (lookup) {
1316
1404
  jsonPath = lookup.jsonPath;
1317
1405
  operandRoot = lookup.source;
1318
1406
  }
1319
1407
  }
1320
1408
  else if (Is.object(operand)) {
1409
+ const typedOperand = operand;
1321
1410
  // Is this an object { "@value": "18", "@type": "xsd:integer" } ?
1322
- const value = operand["@value"];
1323
- const type = operand["@type"];
1411
+ const value = this.extractJsonPathExpressionFromTypedOperand(typedOperand) ??
1412
+ typedOperand["@value"];
1413
+ const type = typedOperand["@type"];
1324
1414
  if (Is.stringValue(type)) {
1325
- const lookup = this.tryResolveOperandLookup(type, value, dataSources);
1415
+ let resolvedType = type;
1416
+ if (this.isTwinJsonPathOperandType(type)) {
1417
+ const dataSourceOverride = typedOperand[DefaultPolicyArbiter._TWIN_JSONPATH_DATA_SOURCE];
1418
+ if (Is.stringValue(dataSourceOverride)) {
1419
+ resolvedType = dataSourceOverride;
1420
+ }
1421
+ }
1422
+ const lookup = this.tryResolveOperandLookup(resolvedType, value, dataSources);
1326
1423
  if (lookup) {
1327
1424
  jsonPath = lookup.jsonPath;
1328
1425
  operandRoot = lookup.source;
@@ -1352,6 +1449,89 @@ export class DefaultPolicyArbiter {
1352
1449
  // Not JSON Path or object value so return as is
1353
1450
  return operand;
1354
1451
  }
1452
+ /**
1453
+ * Determine if a target object uses the canonical twin:jsonPath object format.
1454
+ * @param target The target value.
1455
+ * @returns True if the target is a canonical twin:jsonPath object.
1456
+ * @internal
1457
+ */
1458
+ isTwinJsonPathTarget(target) {
1459
+ return Is.object(target) && this.isTwinJsonPathOperandType(OdrlPolicyHelper.getType(target));
1460
+ }
1461
+ /**
1462
+ * Determine if the operand type is a jsonPath namespace.
1463
+ * @param type The operand type.
1464
+ * @returns True if the type is twin:jsonPath.
1465
+ * @internal
1466
+ */
1467
+ isTwinJsonPathOperandType(type) {
1468
+ return type === DefaultPolicyArbiter._TWIN_JSONPATH;
1469
+ }
1470
+ /**
1471
+ * Normalize canonical jsonPath alias to the legacy jsonpath namespace key.
1472
+ * @param operandTypeOrValue The raw operand type or value.
1473
+ * @returns The normalized operand type/value.
1474
+ * @internal
1475
+ */
1476
+ normalizeTwinJsonPathOperandAlias(operandTypeOrValue) {
1477
+ if (operandTypeOrValue === DefaultPolicyArbiter._TWIN_JSONPATH ||
1478
+ operandTypeOrValue.startsWith(`${DefaultPolicyArbiter._TWIN_JSONPATH}:`)) {
1479
+ const suffix = operandTypeOrValue.slice(DefaultPolicyArbiter._TWIN_JSONPATH.length);
1480
+ return `${DefaultPolicyArbiter._DATA_SOURCE_KEY}${suffix}`;
1481
+ }
1482
+ return operandTypeOrValue;
1483
+ }
1484
+ /**
1485
+ * Extract canonical jsonPath expression from typed operand object.
1486
+ * @param operand The typed operand.
1487
+ * @returns The jsonPath expression if present.
1488
+ * @internal
1489
+ */
1490
+ extractJsonPathExpressionFromTypedOperand(operand) {
1491
+ if (!this.isTwinJsonPathOperandType(operand["@type"])) {
1492
+ return undefined;
1493
+ }
1494
+ const expression = operand[DefaultPolicyArbiter._TWIN_JSONPATH_EXPRESSION];
1495
+ if (Is.stringValue(expression)) {
1496
+ return expression;
1497
+ }
1498
+ const type = operand["@type"];
1499
+ if (type === DefaultPolicyArbiter._TWIN_JSONPATH) {
1500
+ throw new GeneralError(DefaultPolicyArbiter.CLASS_NAME, "jsonPathExpressionMissing", {
1501
+ operand: "rightOperand"
1502
+ });
1503
+ }
1504
+ }
1505
+ /**
1506
+ * Extract canonical jsonPath expression from constraint for leftOperand aliases.
1507
+ * @param constraint The constraint containing the left operand.
1508
+ * @param leftOperand The left operand.
1509
+ * @returns The expression if canonical form is used.
1510
+ * @internal
1511
+ */
1512
+ extractJsonPathExpressionFromConstraint(constraint, leftOperand) {
1513
+ if (!Is.object(constraint)) {
1514
+ return undefined;
1515
+ }
1516
+ if (constraint.leftOperand !== leftOperand) {
1517
+ return undefined;
1518
+ }
1519
+ if (leftOperand.startsWith(`${DefaultPolicyArbiter._TWIN_JSONPATH}:`)) {
1520
+ throw new GeneralError(DefaultPolicyArbiter.CLASS_NAME, "jsonPathExpressionMissing", {
1521
+ operand: "leftOperand"
1522
+ });
1523
+ }
1524
+ if (leftOperand !== DefaultPolicyArbiter._TWIN_JSONPATH) {
1525
+ return undefined;
1526
+ }
1527
+ const expression = constraint[DefaultPolicyArbiter._TWIN_JSONPATH_EXPRESSION];
1528
+ if (Is.stringValue(expression)) {
1529
+ return expression;
1530
+ }
1531
+ throw new GeneralError(DefaultPolicyArbiter.CLASS_NAME, "jsonPathExpressionMissing", {
1532
+ operand: "leftOperand"
1533
+ });
1534
+ }
1355
1535
  /**
1356
1536
  * Evaluate an ODRL operator against resolved operands.
1357
1537
  * @param operator The operator.