openxiangda-devkit-core 2.55.0 → 2.57.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.
@@ -1,3 +1,4 @@
1
+ import { parseNativeParentReadPolicy, validateNativeParentReadPolicies, NativeParentReadPolicyError, NativeDataPolicyExpressionV2Error } from 'openxiangda-contracts/native-compiler';
1
2
  import { assertWorkflowNativeStagePolicy } from 'openxiangda-contracts';
2
3
  import { WORKFLOW_LAUNCH_MAX_INPUTS } from 'openxiangda-contracts';
3
4
  import { validateAuthenticatedPublicRead, AuthenticatedPublicReadContractError } from 'openxiangda-contracts/native-compiler';
@@ -1557,6 +1558,7 @@ export function validateAppConfig(value) {
1557
1558
  const policyFields = resourceFieldTypes.get(string(policy.resourceCode));
1558
1559
  policyRuleNodes(policy, policyPath, diagnostics).forEach(({ rule, path: rulePath }) => {
1559
1560
  const unknownRuleKeys = Object.keys(rule).filter(key => ![
1561
+ 'parentRead',
1560
1562
  'subject',
1561
1563
  'dimensionCode',
1562
1564
  'relationCode',
@@ -1585,8 +1587,17 @@ export function validateAppConfig(value) {
1585
1587
  dimensionCode,
1586
1588
  relationCode,
1587
1589
  dbNow ? 'db_now' : '',
1590
+ rule.parentRead !== undefined ? 'parent_read' : '',
1588
1591
  constantPredicate || nullPredicate ? 'constant' : '',
1589
1592
  ].filter(Boolean);
1593
+ let validParentRead = false;
1594
+ if (rule.parentRead !== undefined) {
1595
+ try {
1596
+ parseNativeParentReadPolicy(rule.parentRead, `${rulePath}.parentRead`);
1597
+ validParentRead = true;
1598
+ }
1599
+ catch { /* The shared cross-resource validator reports the exact failure. */ }
1600
+ }
1590
1601
  const validCurrentUser = subject === 'current_user';
1591
1602
  const validDimension = Boolean(dimensionCode) && dimensionCodes.has(dimensionCode);
1592
1603
  const validRelationship = Boolean(relationCode) && Boolean(string(rule.resourceCode));
@@ -1607,15 +1618,16 @@ export function validateAppConfig(value) {
1607
1618
  ['lt', 'lte', 'gt', 'gte'].includes(operator) &&
1608
1619
  rule.value === undefined &&
1609
1620
  policyFields?.get(string(rule.field)) === 'datetime';
1610
- if (!/^[A-Za-z_][A-Za-z0-9_]{0,127}$/.test(string(rule.field)) ||
1621
+ if ((!validParentRead && !/^[A-Za-z_][A-Za-z0-9_]{0,127}$/.test(string(rule.field))) ||
1611
1622
  modes.length !== 1 ||
1612
1623
  (!validCurrentUser &&
1613
1624
  !validDimension &&
1614
1625
  !validRelationship &&
1615
1626
  !validConstant &&
1616
1627
  !validNull &&
1617
- !validDbNow)) {
1618
- diagnostics.push(diagnostic('APP_CONFIG_AUTHZ_POLICY_RULE_INVALID', '策略规则必须且只能声明 current_user、dimension、relationship、constant/null 或 datetime db_now 中的一种', rulePath));
1628
+ !validDbNow &&
1629
+ !validParentRead)) {
1630
+ diagnostics.push(diagnostic('APP_CONFIG_AUTHZ_POLICY_RULE_INVALID', '策略规则必须且只能声明 current_user、dimension、relationship、constant/null、datetime db_now 或只读 parentRead 中的一种', rulePath));
1619
1631
  }
1620
1632
  if (dbNow && !validDbNow) {
1621
1633
  diagnostics.push(diagnostic('APP_CONFIG_AUTHZ_POLICY_DB_NOW_FIELD_INVALID', 'db_now 只支持 datetime 字段及 lt/lte/gt/gte 操作符', `${rulePath}.field`));
@@ -1640,6 +1652,17 @@ export function validateAppConfig(value) {
1640
1652
  }
1641
1653
  });
1642
1654
  });
1655
+ try {
1656
+ validateNativeParentReadPolicies(dataResources.map(object), policies.map(object), 'authz.dataPolicies');
1657
+ }
1658
+ catch (error) {
1659
+ if (error instanceof NativeParentReadPolicyError) {
1660
+ diagnostics.push(diagnostic(error.code, 'parentRead 必须绑定唯一的真实父子表关系且只用于读取', error.pointer));
1661
+ }
1662
+ else if (!(error instanceof NativeDataPolicyExpressionV2Error))
1663
+ throw error;
1664
+ // Malformed expressions were already reported by policyRuleNodes.
1665
+ }
1643
1666
  scopeSources.forEach((rawSource, sourceIndex) => {
1644
1667
  const source = object(rawSource);
1645
1668
  if (string(source.failureMode) !== 'last_known_good')
@@ -1898,8 +1921,8 @@ export function validateAppConfig(value) {
1898
1921
  targetFields.length > 16 ||
1899
1922
  new Set(sourceFields).size !== sourceFields.length ||
1900
1923
  new Set(targetFields).size !== targetFields.length ||
1901
- sourceFields.some(field => !['file', 'image'].includes(source?.get(field) || '')) ||
1902
- targetFields.some(field => !['file', 'image'].includes(target?.get(field) || '')) ||
1924
+ sourceFields.some(field => !['file', 'image', 'text.rich'].includes(source?.get(field) || '')) ||
1925
+ targetFields.some(field => !['file', 'image', 'text.rich'].includes(target?.get(field) || '')) ||
1903
1926
  sourceFields.length !== targetFields.length ||
1904
1927
  sourceFields.some((field, i) => source?.get(field) !== target?.get(targetFields[i] || '')) ||
1905
1928
  Object.keys(copy).some(key => ![
@@ -2823,6 +2846,7 @@ function validateBackendOperations(rawOperations, declaredResources, declaredRes
2823
2846
  const access = object(operation.platformAccess);
2824
2847
  const allowedAccessKeys = new Set([
2825
2848
  'directory',
2849
+ 'selectedDepartments',
2826
2850
  'managedFiles',
2827
2851
  'managedFileCopies',
2828
2852
  'notification',
@@ -2896,6 +2920,11 @@ function validateBackendOperations(rawOperations, declaredResources, declaredRes
2896
2920
  fields.some(field => !allowedFields.has(string(field))) ||
2897
2921
  Object.keys(directory).some(key => !['mode', 'fields'].includes(key));
2898
2922
  }
2923
+ if (access.selectedDepartments !== undefined) {
2924
+ const departments = object(access.selectedDepartments);
2925
+ const fields = Array.isArray(departments.fields) ? departments.fields : [];
2926
+ invalid ||= !isRecord(access.selectedDepartments) || !fields.includes('name') || fields.length > 4 || new Set(fields).size !== fields.length || fields.some(field => !['name', 'path', 'parent', 'fullPath'].includes(string(field))) || !Number.isSafeInteger(departments.maxIds) || Number(departments.maxIds) < 1 || Number(departments.maxIds) > 50 || Object.keys(departments).some(key => !['fields', 'maxIds'].includes(key));
2927
+ }
2899
2928
  if (access.managedFiles !== undefined) {
2900
2929
  const managedFiles = Array.isArray(access.managedFiles)
2901
2930
  ? access.managedFiles
@@ -2955,8 +2984,8 @@ function validateBackendOperations(rawOperations, declaredResources, declaredRes
2955
2984
  targetFields.length > 16 ||
2956
2985
  new Set(sourceFields).size !== sourceFields.length ||
2957
2986
  new Set(targetFields).size !== targetFields.length ||
2958
- sourceFields.some(field => !['file', 'image'].includes(source?.get(field) || '')) ||
2959
- targetFields.some(field => !['file', 'image'].includes(target?.get(field) || '')) ||
2987
+ sourceFields.some(field => !['file', 'image', 'text.rich'].includes(source?.get(field) || '')) ||
2988
+ targetFields.some(field => !['file', 'image', 'text.rich'].includes(target?.get(field) || '')) ||
2960
2989
  sourceFields.length !== targetFields.length ||
2961
2990
  sourceFields.some((field, i) => source?.get(field) !== target?.get(targetFields[i] || '')) ||
2962
2991
  Object.keys(copy).some(key => ![
@@ -4511,6 +4540,10 @@ export function currentUserDataPolicy(input) {
4511
4540
  };
4512
4541
  }
4513
4542
  export const dataPolicyExpression = {
4543
+ parentRead: (input) => ({
4544
+ parentRead: { resourceCode: input.resourceCode, subtableFieldCode: input.subtableFieldCode },
4545
+ ...(input.roleCodes ? { roleCodes: [...input.roleCodes] } : {}),
4546
+ }),
4514
4547
  allOf: (...values) => ({ allOf: values }),
4515
4548
  anyOf: (...values) => ({ anyOf: values }),
4516
4549
  constant: (input) => ({ ...input }),