@rytass/bpm-core-nestjs-module 0.19.0 → 0.20.0

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 (146) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/package.json +3 -2
  3. package/src/index.d.ts +1 -0
  4. package/src/index.js +1 -0
  5. package/src/index.js.map +1 -1
  6. package/src/lib/bpm/bpm-root-options.d.ts +13 -0
  7. package/src/lib/bpm/bpm-root-options.js.map +1 -1
  8. package/src/lib/bpm/bpm-root.module.d.ts +12 -0
  9. package/src/lib/bpm/bpm-root.module.js +5 -0
  10. package/src/lib/bpm/bpm-root.module.js.map +1 -1
  11. package/src/lib/condition/adapters/cel-legacy.adapter.d.ts +62 -0
  12. package/src/lib/condition/adapters/cel-legacy.adapter.js +141 -0
  13. package/src/lib/condition/adapters/cel-legacy.adapter.js.map +1 -0
  14. package/src/lib/condition/adapters/cel-v2.adapter.d.ts +76 -0
  15. package/src/lib/condition/adapters/cel-v2.adapter.js +371 -0
  16. package/src/lib/condition/adapters/cel-v2.adapter.js.map +1 -0
  17. package/src/lib/condition/condition.service.d.ts +90 -5
  18. package/src/lib/condition/condition.service.js +287 -71
  19. package/src/lib/condition/condition.service.js.map +1 -1
  20. package/src/lib/condition/expression-ast.d.ts +176 -0
  21. package/src/lib/condition/expression-ast.js +1254 -0
  22. package/src/lib/condition/expression-ast.js.map +1 -0
  23. package/src/lib/condition/expression-budget.d.ts +44 -0
  24. package/src/lib/condition/expression-budget.js +61 -0
  25. package/src/lib/condition/expression-budget.js.map +1 -0
  26. package/src/lib/condition/expression-dialect.d.ts +29 -0
  27. package/src/lib/condition/expression-dialect.js +44 -0
  28. package/src/lib/condition/expression-dialect.js.map +1 -0
  29. package/src/lib/condition/expression-differential.corpus.d.ts +34 -0
  30. package/src/lib/condition/expression-differential.corpus.js +995 -0
  31. package/src/lib/condition/expression-differential.corpus.js.map +1 -0
  32. package/src/lib/condition/expression-differential.d.ts +33 -0
  33. package/src/lib/condition/expression-differential.js +48 -0
  34. package/src/lib/condition/expression-differential.js.map +1 -0
  35. package/src/lib/condition/expression-engine-failure.d.ts +15 -0
  36. package/src/lib/condition/expression-engine-failure.js +22 -0
  37. package/src/lib/condition/expression-engine-failure.js.map +1 -0
  38. package/src/lib/condition/expression-errors.d.ts +40 -0
  39. package/src/lib/condition/expression-errors.js +105 -0
  40. package/src/lib/condition/expression-errors.js.map +1 -0
  41. package/src/lib/condition/expression-function-invocation.d.ts +50 -0
  42. package/src/lib/condition/expression-function-invocation.js +290 -0
  43. package/src/lib/condition/expression-function-invocation.js.map +1 -0
  44. package/src/lib/condition/expression-result.walker.d.ts +18 -0
  45. package/src/lib/condition/expression-result.walker.js +152 -0
  46. package/src/lib/condition/expression-result.walker.js.map +1 -0
  47. package/src/lib/condition/expression-runtime.d.ts +70 -0
  48. package/src/lib/condition/expression-runtime.js +134 -0
  49. package/src/lib/condition/expression-runtime.js.map +1 -0
  50. package/src/lib/condition/index.d.ts +4 -0
  51. package/src/lib/condition/index.js +8 -0
  52. package/src/lib/condition/index.js.map +1 -1
  53. package/src/lib/delegation/delegation-rule.entity.d.ts +7 -0
  54. package/src/lib/delegation/delegation-rule.entity.js +10 -0
  55. package/src/lib/delegation/delegation-rule.entity.js.map +1 -1
  56. package/src/lib/delegation/delegation.service.d.ts +7 -1
  57. package/src/lib/delegation/delegation.service.js +22 -5
  58. package/src/lib/delegation/delegation.service.js.map +1 -1
  59. package/src/lib/delegation/dto/delegation-rule.input.d.ts +2 -0
  60. package/src/lib/delegation/dto/delegation-rule.input.js +10 -0
  61. package/src/lib/delegation/dto/delegation-rule.input.js.map +1 -1
  62. package/src/lib/expression-function/expression-function.built-ins.d.ts +9 -0
  63. package/src/lib/expression-function/expression-function.built-ins.js +117 -0
  64. package/src/lib/expression-function/expression-function.built-ins.js.map +1 -0
  65. package/src/lib/expression-function/expression-function.module.d.ts +19 -0
  66. package/src/lib/expression-function/expression-function.module.js +34 -0
  67. package/src/lib/expression-function/expression-function.module.js.map +1 -0
  68. package/src/lib/expression-function/expression-function.provider.d.ts +17 -0
  69. package/src/lib/expression-function/expression-function.provider.js +39 -0
  70. package/src/lib/expression-function/expression-function.provider.js.map +1 -0
  71. package/src/lib/expression-function/expression-function.registry.d.ts +36 -0
  72. package/src/lib/expression-function/expression-function.registry.js +231 -0
  73. package/src/lib/expression-function/expression-function.registry.js.map +1 -0
  74. package/src/lib/expression-function/expression-function.types.d.ts +30 -0
  75. package/src/lib/expression-function/expression-function.types.js +8 -0
  76. package/src/lib/expression-function/expression-function.types.js.map +1 -0
  77. package/src/lib/expression-function/index.d.ts +5 -0
  78. package/src/lib/expression-function/index.js +9 -0
  79. package/src/lib/expression-function/index.js.map +1 -0
  80. package/src/lib/form/form-schema.validator.js +53 -6
  81. package/src/lib/form/form-schema.validator.js.map +1 -1
  82. package/src/lib/form-data-source/form-data-source-value-resolver.service.d.ts +12 -1
  83. package/src/lib/form-data-source/form-data-source-value-resolver.service.js +45 -26
  84. package/src/lib/form-data-source/form-data-source-value-resolver.service.js.map +1 -1
  85. package/src/lib/form-data-source/form-data-source.expression.d.ts +95 -0
  86. package/src/lib/form-data-source/form-data-source.expression.js +267 -0
  87. package/src/lib/form-data-source/form-data-source.expression.js.map +1 -0
  88. package/src/lib/form-data-source/form-data-source.module.js +4 -0
  89. package/src/lib/form-data-source/form-data-source.module.js.map +1 -1
  90. package/src/lib/form-data-source/form-data-source.service.d.ts +22 -3
  91. package/src/lib/form-data-source/form-data-source.service.js +78 -18
  92. package/src/lib/form-data-source/form-data-source.service.js.map +1 -1
  93. package/src/lib/form-data-source/form-data-source.validation.d.ts +47 -4
  94. package/src/lib/form-data-source/form-data-source.validation.js +151 -23
  95. package/src/lib/form-data-source/form-data-source.validation.js.map +1 -1
  96. package/src/lib/migrations/0000000030000-delegation-scope-condition-dialect.d.ts +22 -0
  97. package/src/lib/migrations/0000000030000-delegation-scope-condition-dialect.js +76 -0
  98. package/src/lib/migrations/0000000030000-delegation-scope-condition-dialect.js.map +1 -0
  99. package/src/lib/migrations/index.d.ts +1 -0
  100. package/src/lib/migrations/index.js +3 -0
  101. package/src/lib/migrations/index.js.map +1 -1
  102. package/src/lib/notification/notification.service.d.ts +1 -1
  103. package/src/lib/notification/notification.service.js +7 -3
  104. package/src/lib/notification/notification.service.js.map +1 -1
  105. package/src/lib/template/dto/expression-designer.dto.d.ts +107 -0
  106. package/src/lib/template/dto/expression-designer.dto.js +415 -0
  107. package/src/lib/template/dto/expression-designer.dto.js.map +1 -0
  108. package/src/lib/template/expression-designer.queries.d.ts +16 -0
  109. package/src/lib/template/expression-designer.queries.js +66 -0
  110. package/src/lib/template/expression-designer.queries.js.map +1 -0
  111. package/src/lib/template/expression-designer.service.d.ts +72 -0
  112. package/src/lib/template/expression-designer.service.js +543 -0
  113. package/src/lib/template/expression-designer.service.js.map +1 -0
  114. package/src/lib/template/index.d.ts +2 -0
  115. package/src/lib/template/index.js +2 -0
  116. package/src/lib/template/index.js.map +1 -1
  117. package/src/lib/template/template.module.js +4 -0
  118. package/src/lib/template/template.module.js.map +1 -1
  119. package/src/lib/template/template.service.d.ts +37 -1
  120. package/src/lib/template/template.service.js +317 -29
  121. package/src/lib/template/template.service.js.map +1 -1
  122. package/src/lib/template/workflow-definition.validator.d.ts +7 -0
  123. package/src/lib/template/workflow-definition.validator.js +26 -2
  124. package/src/lib/template/workflow-definition.validator.js.map +1 -1
  125. package/src/lib/workflow-engine/time-budget.d.ts +13 -0
  126. package/src/lib/workflow-engine/time-budget.js +58 -0
  127. package/src/lib/workflow-engine/time-budget.js.map +1 -0
  128. package/src/lib/workflow-engine/workflow-engine.service.d.ts +24 -0
  129. package/src/lib/workflow-engine/workflow-engine.service.js +240 -65
  130. package/src/lib/workflow-engine/workflow-engine.service.js.map +1 -1
  131. package/src/lib/workflow-engine/workflow-service-task-dispatcher.token.js +4 -0
  132. package/src/lib/workflow-engine/workflow-service-task-dispatcher.token.js.map +1 -1
  133. package/src/lib/workflow-webhook/workflow-webhook-delivery.service.d.ts +3 -1
  134. package/src/lib/workflow-webhook/workflow-webhook-delivery.service.js +11 -2
  135. package/src/lib/workflow-webhook/workflow-webhook-delivery.service.js.map +1 -1
  136. package/src/lib/workflow-webhook/workflow-webhook-enqueue.d.ts +8 -1
  137. package/src/lib/workflow-webhook/workflow-webhook-enqueue.js +70 -27
  138. package/src/lib/workflow-webhook/workflow-webhook-enqueue.js.map +1 -1
  139. package/src/lib/workflow-webhook/workflow-webhook-expression.d.ts +57 -0
  140. package/src/lib/workflow-webhook/workflow-webhook-expression.js +123 -0
  141. package/src/lib/workflow-webhook/workflow-webhook-expression.js.map +1 -0
  142. package/src/lib/workflow-webhook/workflow-webhook.module.js +4 -0
  143. package/src/lib/workflow-webhook/workflow-webhook.module.js.map +1 -1
  144. package/src/lib/workflow-webhook/workflow-webhook.validator.d.ts +7 -0
  145. package/src/lib/workflow-webhook/workflow-webhook.validator.js +29 -6
  146. package/src/lib/workflow-webhook/workflow-webhook.validator.js.map +1 -1
@@ -1,6 +1,9 @@
1
1
  import { ModuleRef } from '@nestjs/core';
2
+ import { ExpressionUsagePoint } from '@rytass/bpm-core-shared';
3
+ import { WorkflowDefinition } from '@rytass/bpm-core-shared/workflow';
2
4
  import { EntityManager, Repository } from 'typeorm';
3
5
  import { ConditionService } from '../condition/condition.service';
6
+ import { BPMExpressionFunctionRegistry } from '../expression-function/expression-function.types';
4
7
  import { FormDefinitionVersionEntity } from '../form/form-definition-version.entity';
5
8
  import { FormService } from '../form/form.service';
6
9
  import { ComposeApprovalTemplateWithFormInput } from './dto/compose-approval-template.input';
@@ -32,8 +35,9 @@ export declare class TemplateService {
32
35
  private readonly conditionService;
33
36
  private readonly formService;
34
37
  private readonly moduleRef;
38
+ private readonly expressionFunctionRegistry?;
35
39
  private readonly logger;
36
- constructor(templateRepository: Repository<ApprovalTemplateEntity>, templateCategoryRepository: Repository<ApprovalTemplateCategoryEntity>, templateVersionRepository: Repository<ApprovalTemplateVersionEntity>, formDefinitionVersionRepository: Repository<FormDefinitionVersionEntity>, conditionService: ConditionService, formService: FormService, moduleRef: ModuleRef);
40
+ constructor(templateRepository: Repository<ApprovalTemplateEntity>, templateCategoryRepository: Repository<ApprovalTemplateCategoryEntity>, templateVersionRepository: Repository<ApprovalTemplateVersionEntity>, formDefinitionVersionRepository: Repository<FormDefinitionVersionEntity>, conditionService: ConditionService, formService: FormService, moduleRef: ModuleRef, expressionFunctionRegistry?: (BPMExpressionFunctionRegistry | null) | undefined);
37
41
  createApprovalTemplate(input: CreateApprovalTemplateInput, manager?: EntityManager): Promise<ApprovalTemplateEntity>;
38
42
  private createApprovalTemplateWithManager;
39
43
  /**
@@ -87,6 +91,21 @@ export declare class TemplateService {
87
91
  private notifyTemplateObserver;
88
92
  private readHostObserver;
89
93
  private validatePublishableVersion;
94
+ /** The cel-legacy publish lint, unchanged (docs/24 §1). */
95
+ private lintCelLegacyExpressions;
96
+ /**
97
+ * The cel-v2 publish lint (ADR 22 §3.4, §5): every expression on its AST
98
+ * with its use point's roots, the bound form's fields and types and the
99
+ * function registry, plus the `SET_FORM_FIELD` write targets. Warnings do
100
+ * not refuse a publish; the designer reads them through
101
+ * `ConditionService.lintExpressionIssues`.
102
+ */
103
+ private lintCelV2Expressions;
104
+ /**
105
+ * The functions a version calls that the registry no longer has, as lint
106
+ * lines. Only cel-v2 can call registered functions at all.
107
+ */
108
+ private lintRegisteredFunctions;
90
109
  /**
91
110
  * Publish rules that need the webhook endpoint catalog (ADR 18 §4).
92
111
  *
@@ -110,4 +129,21 @@ export declare class TemplateService {
110
129
  private readNextVersionNumber;
111
130
  private parseWorkflowDefinitionOrThrow;
112
131
  }
132
+ export interface ExpressionSource {
133
+ readonly expression: string | null | undefined;
134
+ readonly label: string;
135
+ }
136
+ export interface UsagePointExpression extends ExpressionSource {
137
+ /** The top-level field a `SET_FORM_FIELD` value is written to. */
138
+ readonly targetFieldKey: string | null;
139
+ readonly usagePoint: ExpressionUsagePoint;
140
+ }
141
+ /**
142
+ * Every expression of a version with its use point (docs/24 §2.3). Unlike
143
+ * the cel-legacy readers this includes the NOTIFY recipients expression,
144
+ * which is evaluated like an approver expression. A draft is not
145
+ * shape-checked, so every read is guarded. Also read by the designer's
146
+ * expression analysis and dialect upgrade check (Phase 23 P4).
147
+ */
148
+ export declare function readUsagePointExpressions(definition: WorkflowDefinition, initiatorPolicyCel: string | null): readonly UsagePointExpression[];
113
149
  export {};
@@ -2,6 +2,7 @@
2
2
  var TemplateService_1;
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.TemplateService = void 0;
5
+ exports.readUsagePointExpressions = readUsagePointExpressions;
5
6
  const tslib_1 = require("tslib");
6
7
  const common_1 = require("@nestjs/common");
7
8
  const core_1 = require("@nestjs/core");
@@ -11,6 +12,9 @@ const form_1 = require("@rytass/bpm-core-shared/form");
11
12
  const workflow_graph_1 = require("@rytass/bpm-core-shared/workflow-graph");
12
13
  const typeorm_2 = require("typeorm");
13
14
  const condition_service_1 = require("../condition/condition.service");
15
+ const expression_dialect_1 = require("../condition/expression-dialect");
16
+ const expression_ast_1 = require("../condition/expression-ast");
17
+ const expression_function_types_1 = require("../expression-function/expression-function.types");
14
18
  const form_table_reference_1 = require("../form/form-table-reference");
15
19
  const form_definition_entity_1 = require("../form/form-definition.entity");
16
20
  const form_definition_version_entity_1 = require("../form/form-definition-version.entity");
@@ -43,7 +47,7 @@ const WORKFLOW_EXPRESSION_ROOT_IDENTIFIERS = [
43
47
  'instance',
44
48
  ];
45
49
  let TemplateService = TemplateService_1 = class TemplateService {
46
- constructor(templateRepository, templateCategoryRepository, templateVersionRepository, formDefinitionVersionRepository, conditionService, formService, moduleRef) {
50
+ constructor(templateRepository, templateCategoryRepository, templateVersionRepository, formDefinitionVersionRepository, conditionService, formService, moduleRef, expressionFunctionRegistry) {
47
51
  this.templateRepository = templateRepository;
48
52
  this.templateCategoryRepository = templateCategoryRepository;
49
53
  this.templateVersionRepository = templateVersionRepository;
@@ -51,6 +55,7 @@ let TemplateService = TemplateService_1 = class TemplateService {
51
55
  this.conditionService = conditionService;
52
56
  this.formService = formService;
53
57
  this.moduleRef = moduleRef;
58
+ this.expressionFunctionRegistry = expressionFunctionRegistry;
54
59
  this.logger = new common_1.Logger(TemplateService_1.name);
55
60
  }
56
61
  async createApprovalTemplate(input, manager) {
@@ -89,7 +94,7 @@ let TemplateService = TemplateService_1 = class TemplateService {
89
94
  status: template_enums_1.ApprovalTemplateVersionStatusEnum.DRAFT,
90
95
  templateId: template.id,
91
96
  version: 1,
92
- workflowDefinition: workflow_definition_validator_1.EMPTY_WORKFLOW_DEFINITION,
97
+ workflowDefinition: workflow_definition_validator_1.NEW_TEMPLATE_WORKFLOW_DEFINITION,
93
98
  }));
94
99
  // As in `updateApprovalTemplate`: `categoryId` is no longer written by
95
100
  // `save()`, so the in-memory entity would come back without it even though
@@ -477,6 +482,14 @@ let TemplateService = TemplateService_1 = class TemplateService {
477
482
  if (target.status === template_enums_1.ApprovalTemplateVersionStatusEnum.DRAFT) {
478
483
  throw new common_1.ConflictException('Draft approval template versions cannot be rollback targets');
479
484
  }
485
+ // A rollback is a recovery path: the version passed the lint of its day
486
+ // and is not linted again, since a rule tightened since could refuse it
487
+ // (ADR 22 §5, docs/24 §1). The one check is that every function it calls
488
+ // is still registered; without it every case would fail on evaluation.
489
+ const unregisteredFunctionErrors = this.lintRegisteredFunctions(target.workflowDefinition, target.initiatorPolicyCel);
490
+ if (unregisteredFunctionErrors.length) {
491
+ throw new common_1.BadRequestException(unregisteredFunctionErrors.join('; '));
492
+ }
480
493
  const rolledBack = await this.templateRepository.manager.transaction(async (manager) => {
481
494
  const templateRepository = manager.getRepository(approval_template_entity_1.ApprovalTemplateEntity);
482
495
  const versionRepository = manager.getRepository(approval_template_version_entity_1.ApprovalTemplateVersionEntity);
@@ -562,24 +575,22 @@ let TemplateService = TemplateService_1 = class TemplateService {
562
575
  throw new common_1.BadRequestException('Approval template can only bind a published form definition version');
563
576
  }
564
577
  const workflowResult = (0, workflow_definition_validator_1.lintWorkflowDefinition)(version.workflowDefinition);
565
- const conditionErrors = [
566
- ...this.conditionService.lintExpressions([
567
- {
568
- expression: version.initiatorPolicyCel,
569
- label: 'initiatorPolicyCel',
570
- },
571
- ], { allowedRootIdentifiers: INITIATOR_POLICY_ROOT_IDENTIFIERS }),
572
- ...this.conditionService.lintExpressions(readNodeConditionExpressions(version.workflowDefinition), { allowedRootIdentifiers: WORKFLOW_EXPRESSION_ROOT_IDENTIFIERS }),
573
- // Edge conditions also see the decision just made on the node they
574
- // leave (workflow-engine `evaluateWorkflowEdge` and its dry-run twin).
575
- ...this.conditionService.lintExpressions(readEdgeConditionExpressions(version.workflowDefinition), {
576
- allowedRootIdentifiers: [
577
- ...WORKFLOW_EXPRESSION_ROOT_IDENTIFIERS,
578
- 'lastDecision',
579
- ],
580
- }),
581
- ];
582
- const tableReferenceErrors = lintWorkflowTableReferences(version.workflowDefinition, version.initiatorPolicyCel, formVersion.schema);
578
+ // An unknown dialect is refused by `lintWorkflowDefinition`; its
579
+ // expressions are not parsed with a guessed engine.
580
+ const dialect = readPublishDialect(version.workflowDefinition);
581
+ const fieldReader = createExpressionFieldReader(dialect ?? expression_dialect_1.DEFAULT_EXPRESSION_DIALECT);
582
+ const conditionErrors = dialect === 'cel-v2'
583
+ ? this.lintCelV2Expressions(version, formVersion.schema)
584
+ : dialect === null
585
+ ? []
586
+ : this.lintCelLegacyExpressions(version);
587
+ // ADR 22 §5.5 names no dialect, and a write path is not CEL: its reserved
588
+ // segments are refused in either. That it names a declared field is
589
+ // checked for cel-v2 only (`lintCelV2Expressions`).
590
+ const setFormFieldPathErrors = dialect === null
591
+ ? []
592
+ : lintSetFormFieldReservedSegments(version.workflowDefinition);
593
+ const tableReferenceErrors = lintWorkflowTableReferences(version.workflowDefinition, version.initiatorPolicyCel, formVersion.schema, fieldReader);
583
594
  const memberReferenceErrors = lintWorkflowMemberReferences(version.workflowDefinition, formVersion.schema);
584
595
  const orgUnitReferenceErrors = lintWorkflowOrgUnitReferences(version.workflowDefinition, formVersion.schema);
585
596
  // The graph checks read a sound graph (the publish lint refuses cycles
@@ -589,11 +600,12 @@ let TemplateService = TemplateService_1 = class TemplateService {
589
600
  ? lintWorkflowNodeActorSources(version.workflowDefinition)
590
601
  : [];
591
602
  const approverFilledFieldErrors = (0, bpm_core_shared_1.readFormFieldAccessIssues)(version.workflowDefinition, formVersion.schema).map((issue) => issue.message);
592
- const approverFilledReadErrors = lintApproverFilledFieldReads(version.workflowDefinition);
603
+ const approverFilledReadErrors = lintApproverFilledFieldReads(version.workflowDefinition, fieldReader);
593
604
  const webhookErrors = await this.lintWorkflowWebhooks(version.workflowDefinition, formVersion.schema);
594
605
  const errors = [
595
606
  ...workflowResult.errors,
596
607
  ...conditionErrors,
608
+ ...setFormFieldPathErrors,
597
609
  ...tableReferenceErrors,
598
610
  ...memberReferenceErrors,
599
611
  ...orgUnitReferenceErrors,
@@ -606,6 +618,69 @@ let TemplateService = TemplateService_1 = class TemplateService {
606
618
  throw new common_1.BadRequestException(errors.join('; '));
607
619
  }
608
620
  }
621
+ /** The cel-legacy publish lint, unchanged (docs/24 §1). */
622
+ lintCelLegacyExpressions(version) {
623
+ return [
624
+ ...this.conditionService.lintExpressions([
625
+ {
626
+ expression: version.initiatorPolicyCel,
627
+ label: 'initiatorPolicyCel',
628
+ },
629
+ ], { allowedRootIdentifiers: INITIATOR_POLICY_ROOT_IDENTIFIERS }),
630
+ ...this.conditionService.lintExpressions(readNodeConditionExpressions(version.workflowDefinition), { allowedRootIdentifiers: WORKFLOW_EXPRESSION_ROOT_IDENTIFIERS }),
631
+ // Edge conditions also see the decision just made on the node they
632
+ // leave (workflow-engine `evaluateWorkflowEdge` and its dry-run twin).
633
+ ...this.conditionService.lintExpressions(readEdgeConditionExpressions(version.workflowDefinition), {
634
+ allowedRootIdentifiers: [
635
+ ...WORKFLOW_EXPRESSION_ROOT_IDENTIFIERS,
636
+ 'lastDecision',
637
+ ],
638
+ }),
639
+ ];
640
+ }
641
+ /**
642
+ * The cel-v2 publish lint (ADR 22 §3.4, §5): every expression on its AST
643
+ * with its use point's roots, the bound form's fields and types and the
644
+ * function registry, plus the `SET_FORM_FIELD` write targets. Warnings do
645
+ * not refuse a publish; the designer reads them through
646
+ * `ConditionService.lintExpressionIssues`.
647
+ */
648
+ lintCelV2Expressions(version, formSchema) {
649
+ const fields = new Map((formSchema?.fields ?? []).map((entry) => [entry.fieldKey, entry]));
650
+ return [
651
+ ...readUsagePointExpressions(version.workflowDefinition, version.initiatorPolicyCel).flatMap(({ expression, label, targetFieldKey, usagePoint }) => this.conditionService.lintExpression(expression, label, {
652
+ dialect: 'cel-v2',
653
+ formSchema,
654
+ functionRegistry: this.expressionFunctionRegistry ?? null,
655
+ targetField: targetFieldKey === null ? null : (fields.get(targetFieldKey) ?? null),
656
+ usagePoint,
657
+ })),
658
+ ...lintSetFormFieldDeclaredFields(version.workflowDefinition, formSchema),
659
+ ];
660
+ }
661
+ /**
662
+ * The functions a version calls that the registry no longer has, as lint
663
+ * lines. Only cel-v2 can call registered functions at all.
664
+ */
665
+ lintRegisteredFunctions(definition, initiatorPolicyCel) {
666
+ if (readPublishDialect(definition) !== 'cel-v2') {
667
+ return [];
668
+ }
669
+ const registry = this.expressionFunctionRegistry ?? null;
670
+ return readUsagePointExpressions(definition, initiatorPolicyCel).flatMap(({ expression, label }) => {
671
+ const names = typeof expression === 'string'
672
+ ? ((0, expression_ast_1.readExpressionHostFunctionNames)(expression.trim(), 'cel-v2') ?? [])
673
+ : [];
674
+ // Same name rule and registry isolation as the publish lint.
675
+ const missing = (0, expression_ast_1.readUnavailableExpressionFunctions)(names, registry);
676
+ return missing.map((name) => (0, bpm_core_shared_1.formatExpressionLintIssue)({
677
+ code: 'EXPR_FUNCTION_NOT_REGISTERED',
678
+ label,
679
+ severity: 'ERROR',
680
+ subject: name,
681
+ }));
682
+ });
683
+ }
609
684
  /**
610
685
  * Publish rules that need the webhook endpoint catalog (ADR 18 §4).
611
686
  *
@@ -624,6 +699,9 @@ let TemplateService = TemplateService_1 = class TemplateService {
624
699
  }
625
700
  })();
626
701
  return (0, workflow_webhook_validator_1.lintWorkflowWebhookTargets)({
702
+ // The injected service: the NOTIFY `EXPRESSION` lint sees the same
703
+ // function registry the engine evaluates with (docs/23 P4/P5 交接).
704
+ conditionService: this.conditionService,
627
705
  definition,
628
706
  formSchema,
629
707
  hasEndpointSources: Boolean(webhookService?.hasEndpointSources()),
@@ -737,13 +815,15 @@ exports.TemplateService = TemplateService = TemplateService_1 = tslib_1.__decora
737
815
  tslib_1.__param(1, (0, typeorm_1.InjectRepository)(approval_template_category_entity_1.ApprovalTemplateCategoryEntity)),
738
816
  tslib_1.__param(2, (0, typeorm_1.InjectRepository)(approval_template_version_entity_1.ApprovalTemplateVersionEntity)),
739
817
  tslib_1.__param(3, (0, typeorm_1.InjectRepository)(form_definition_version_entity_1.FormDefinitionVersionEntity)),
818
+ tslib_1.__param(7, (0, common_1.Optional)()),
819
+ tslib_1.__param(7, (0, common_1.Inject)(expression_function_types_1.BPM_EXPRESSION_FUNCTION_REGISTRY)),
740
820
  tslib_1.__metadata("design:paramtypes", [typeorm_2.Repository,
741
821
  typeorm_2.Repository,
742
822
  typeorm_2.Repository,
743
823
  typeorm_2.Repository,
744
824
  condition_service_1.ConditionService,
745
825
  form_service_1.FormService,
746
- core_1.ModuleRef])
826
+ core_1.ModuleRef, Object])
747
827
  ], TemplateService);
748
828
  function parseOptionalJsonObject(value) {
749
829
  if (!value) {
@@ -835,14 +915,14 @@ function createApprovalTemplateCategoryWhere(options) {
835
915
  * a `conditionFieldKey` of `items.qty` simply resolves to `undefined` at
836
916
  * runtime. Both would fail silently rather than loudly.
837
917
  */
838
- function lintWorkflowTableReferences(definition, initiatorPolicyCel, schema) {
918
+ function lintWorkflowTableReferences(definition, initiatorPolicyCel, schema, fieldReader) {
839
919
  const tableFieldKeys = (0, form_table_reference_1.readTableFieldKeys)(schema);
840
920
  if (!tableFieldKeys.size) {
841
921
  return [];
842
922
  }
843
923
  const expressionErrors = readConditionExpressions(definition, initiatorPolicyCel).flatMap(({ expression, label }) => typeof expression === 'string'
844
924
  ? [...tableFieldKeys]
845
- .filter((tableKey) => (0, form_table_reference_1.referencesTableInternals)(expression, tableKey))
925
+ .filter((tableKey) => fieldReader.readsTableInternals(expression, tableKey))
846
926
  .map((tableKey) => `${label} must not reference table field internals: ${tableKey}`)
847
927
  : []);
848
928
  const structuredErrors = definition.edges
@@ -997,7 +1077,7 @@ function lintWorkflowNodeActorSources(definition) {
997
1077
  * The definition may hold a cycle (the cycle lint reports it in the same
998
1078
  * pass), which the visited set in `readReachableWorkflowNodeIds` copes with.
999
1079
  */
1000
- function lintApproverFilledFieldReads(definition) {
1080
+ function lintApproverFilledFieldReads(definition, fieldReader) {
1001
1081
  const fieldKeys = [...(0, bpm_core_shared_1.readApproverFilledFieldKeys)(definition)];
1002
1082
  if (!fieldKeys.length) {
1003
1083
  return [];
@@ -1026,11 +1106,11 @@ function lintApproverFilledFieldReads(definition) {
1026
1106
  const nodeLabels = nodeSources
1027
1107
  .filter(({ expression, nodeId }) => nodeIdsReadingEarly.has(nodeId) &&
1028
1108
  typeof expression === 'string' &&
1029
- (0, form_table_reference_1.referencesFormField)(expression, fieldKey))
1109
+ fieldReader.readsField(expression, fieldKey))
1030
1110
  .map(({ label }) => label);
1031
1111
  const edgeLabels = definition.edges
1032
1112
  .filter((edge) => earlyNodeIds.has(edge.source))
1033
- .flatMap((edge) => readEdgeFieldReadLabels(edge, fieldKey));
1113
+ .flatMap((edge) => readEdgeFieldReadLabels(edge, fieldKey, fieldReader));
1034
1114
  return [...nodeLabels, ...edgeLabels].map((label) => workflow_graph_1.WORKFLOW_LINT_MESSAGES.approverFilledFieldReadEarly(label, fieldKey));
1035
1115
  });
1036
1116
  }
@@ -1040,12 +1120,13 @@ function hasEntryCondition(node) {
1040
1120
  }
1041
1121
  // The label of the one line an edge earns for `fieldKey`: the CEL condition
1042
1122
  // wins over the structured condition, so an edge carrying both reads once.
1043
- function readEdgeFieldReadLabels(edge, fieldKey) {
1123
+ function readEdgeFieldReadLabels(edge, fieldKey, fieldReader) {
1044
1124
  // A draft's edge may carry no `data` at all.
1045
1125
  const data = edge.data;
1046
1126
  const condition = data?.condition;
1047
1127
  const conditionFieldKey = data?.conditionFieldKey;
1048
- if (typeof condition === 'string' && (0, form_table_reference_1.referencesFormField)(condition, fieldKey)) {
1128
+ if (typeof condition === 'string' &&
1129
+ fieldReader.readsField(condition, fieldKey)) {
1049
1130
  return [workflow_graph_1.WORKFLOW_EXPRESSION_LABELS.edgeCondition(edge.id)];
1050
1131
  }
1051
1132
  return typeof conditionFieldKey === 'string' &&
@@ -1143,6 +1224,213 @@ function readNodeExpressions(node) {
1143
1224
  }
1144
1225
  return [];
1145
1226
  }
1227
+ /**
1228
+ * The dialect a definition is published with: the default when it names
1229
+ * none, `null` when it names one this version does not know (refused by
1230
+ * `lintWorkflowDefinition`).
1231
+ */
1232
+ function readPublishDialect(definition) {
1233
+ const value = definition?.expressionDialect;
1234
+ if (value === undefined || value === null) {
1235
+ return expression_dialect_1.DEFAULT_EXPRESSION_DIALECT;
1236
+ }
1237
+ return (0, expression_dialect_1.isExpressionDialect)(value) ? value : null;
1238
+ }
1239
+ const TEXTUAL_FIELD_READER = {
1240
+ readsField: form_table_reference_1.referencesFormField,
1241
+ readsTableInternals: form_table_reference_1.referencesTableInternals,
1242
+ };
1243
+ function createExpressionFieldReader(dialect) {
1244
+ if (dialect !== 'cel-v2') {
1245
+ return TEXTUAL_FIELD_READER;
1246
+ }
1247
+ const cache = new Map();
1248
+ const read = (expression) => {
1249
+ if (!cache.has(expression)) {
1250
+ const result = (0, expression_ast_1.readExpressionReferences)(expression.trim(), 'cel-v2');
1251
+ cache.set(expression, result.status === 'OK' ? result.references : null);
1252
+ }
1253
+ return cache.get(expression) ?? null;
1254
+ };
1255
+ return {
1256
+ readsField: (expression, fieldKey) => {
1257
+ const references = read(expression);
1258
+ return references
1259
+ ? (0, expression_ast_1.readsFormField)(references, fieldKey)
1260
+ : (0, form_table_reference_1.referencesFormField)(expression, fieldKey);
1261
+ },
1262
+ readsTableInternals: (expression, tableKey) => {
1263
+ const references = read(expression);
1264
+ return references
1265
+ ? (0, expression_ast_1.readsTableInternals)(references, tableKey)
1266
+ : (0, form_table_reference_1.referencesTableInternals)(expression, tableKey);
1267
+ },
1268
+ };
1269
+ }
1270
+ /**
1271
+ * Every expression of a version with its use point (docs/24 §2.3). Unlike
1272
+ * the cel-legacy readers this includes the NOTIFY recipients expression,
1273
+ * which is evaluated like an approver expression. A draft is not
1274
+ * shape-checked, so every read is guarded. Also read by the designer's
1275
+ * expression analysis and dialect upgrade check (Phase 23 P4).
1276
+ */
1277
+ function readUsagePointExpressions(definition, initiatorPolicyCel) {
1278
+ const nodes = Array.isArray(definition?.nodes)
1279
+ ? definition.nodes
1280
+ : [];
1281
+ const edges = Array.isArray(definition?.edges)
1282
+ ? definition.edges
1283
+ : [];
1284
+ const entry = (expression, label, usagePoint, targetFieldKey = null) => ({ expression, label, targetFieldKey, usagePoint });
1285
+ return [
1286
+ entry(initiatorPolicyCel, 'initiatorPolicyCel', 'INITIATOR_POLICY'),
1287
+ ...nodes.flatMap((node) => {
1288
+ const data = node?.data;
1289
+ if (!isRecord(data) || typeof node.id !== 'string') {
1290
+ return [];
1291
+ }
1292
+ const entryCondition = entry(readOptionalString(data['entryCondition']), workflow_graph_1.WORKFLOW_EXPRESSION_LABELS.nodeEntryCondition(node.id), 'ENTRY_CONDITION');
1293
+ if (node.type === 'userTask') {
1294
+ const resolver = data['approverResolver'];
1295
+ return [
1296
+ entryCondition,
1297
+ ...(isRecord(resolver) && resolver['type'] === 'EXPRESSION'
1298
+ ? [
1299
+ entry(readOptionalString(resolver['expression']), workflow_graph_1.WORKFLOW_EXPRESSION_LABELS.nodeApproverResolverExpression(node.id), 'APPROVER_RESOLVER'),
1300
+ ]
1301
+ : []),
1302
+ ];
1303
+ }
1304
+ if (node.type !== 'serviceTask') {
1305
+ return [];
1306
+ }
1307
+ const action = data['action'];
1308
+ if (!isRecord(action)) {
1309
+ return [entryCondition];
1310
+ }
1311
+ if (action['type'] === 'WEBHOOK') {
1312
+ return [
1313
+ entryCondition,
1314
+ entry(readOptionalString(action['payload']), workflow_graph_1.WORKFLOW_EXPRESSION_LABELS.nodeActionPayload(node.id), 'WEBHOOK_PAYLOAD'),
1315
+ ];
1316
+ }
1317
+ if (action['type'] === 'SET_FORM_FIELD') {
1318
+ const fieldPath = action['fieldPath'];
1319
+ return [
1320
+ entryCondition,
1321
+ entry(readOptionalString(action['value']), workflow_graph_1.WORKFLOW_EXPRESSION_LABELS.nodeActionValue(node.id), 'SERVICE_FORM_FIELD_VALUE', typeof fieldPath === 'string' ? readWholeFieldTarget(fieldPath) : null),
1322
+ ];
1323
+ }
1324
+ const recipients = action['recipients'];
1325
+ return action['type'] === 'NOTIFY' &&
1326
+ isRecord(recipients) &&
1327
+ recipients['type'] === 'EXPRESSION'
1328
+ ? [
1329
+ entryCondition,
1330
+ entry(readOptionalString(recipients['expression']), workflow_graph_1.WORKFLOW_EXPRESSION_LABELS.nodeActionRecipientsExpression(node.id), 'APPROVER_RESOLVER'),
1331
+ ]
1332
+ : [entryCondition];
1333
+ }),
1334
+ ...edges.flatMap((edge) => {
1335
+ const data = edge?.data;
1336
+ return typeof edge?.id === 'string' && isRecord(data)
1337
+ ? [
1338
+ entry(readOptionalString(data['condition']), workflow_graph_1.WORKFLOW_EXPRESSION_LABELS.edgeCondition(edge.id), 'FLOW_CONDITION'),
1339
+ ]
1340
+ : [];
1341
+ }),
1342
+ ];
1343
+ }
1344
+ /**
1345
+ * The field a `SET_FORM_FIELD` path writes as a whole: `amount`, `form.amount`.
1346
+ * A deeper path (`meta.code`) writes inside a field, whose declared type then
1347
+ * says nothing about the value, so it has no target.
1348
+ */
1349
+ function readWholeFieldTarget(fieldPath) {
1350
+ const segments = fieldPath
1351
+ .split('.')
1352
+ .map((segment) => segment.trim())
1353
+ .filter(Boolean);
1354
+ const fieldSegments = segments[0] === 'form' || segments[0] === 'formData'
1355
+ ? segments.slice(1)
1356
+ : segments;
1357
+ return fieldSegments.length === 1 && !/[[\]]/u.test(fieldSegments[0])
1358
+ ? fieldSegments[0]
1359
+ : null;
1360
+ }
1361
+ function readOptionalString(value) {
1362
+ return typeof value === 'string' || value === null ? value : undefined;
1363
+ }
1364
+ /**
1365
+ * Every non-blank `SET_FORM_FIELD` write path in a definition. A blank path is
1366
+ * reported by the structural lint already.
1367
+ */
1368
+ function readSetFormFieldPaths(definition) {
1369
+ const nodes = Array.isArray(definition?.nodes)
1370
+ ? definition.nodes
1371
+ : [];
1372
+ return nodes.flatMap((node) => {
1373
+ const data = node?.data;
1374
+ const action = isRecord(data) ? data['action'] : undefined;
1375
+ const fieldPath = isRecord(action) && action['type'] === 'SET_FORM_FIELD'
1376
+ ? action['fieldPath']
1377
+ : undefined;
1378
+ if (typeof fieldPath !== 'string' || !fieldPath.trim()) {
1379
+ return [];
1380
+ }
1381
+ return [
1382
+ {
1383
+ fieldPath,
1384
+ label: `workflow.nodes.${node.id}.action.fieldPath`,
1385
+ reserved: [
1386
+ ...new Set(fieldPath
1387
+ .split(/[.[\]"']/u)
1388
+ .map((segment) => segment.trim())
1389
+ .filter((segment) => expression_ast_1.RESERVED_PATH_SEGMENTS.has(segment))),
1390
+ ],
1391
+ },
1392
+ ];
1393
+ });
1394
+ }
1395
+ /**
1396
+ * ADR 22 §5.5, the half that holds in both dialects: a `SET_FORM_FIELD` path
1397
+ * has no `__proto__`, `constructor` or `prototype` segment.
1398
+ */
1399
+ function lintSetFormFieldReservedSegments(definition) {
1400
+ return readSetFormFieldPaths(definition).flatMap(({ label, reserved }) => reserved.map((segment) => (0, bpm_core_shared_1.formatExpressionLintIssue)({
1401
+ code: 'EXPR_RESERVED_PATH_SEGMENT',
1402
+ label,
1403
+ severity: 'ERROR',
1404
+ subject: segment,
1405
+ })));
1406
+ }
1407
+ /**
1408
+ * ADR 22 §3.4 / §5.5 for cel-v2 definitions: a `SET_FORM_FIELD` writes a
1409
+ * field the bound form declares. A path with a reserved segment is reported
1410
+ * by `lintSetFormFieldReservedSegments` instead. cel-legacy does not get this
1411
+ * half yet: a fork of an older version may write a field its form dropped.
1412
+ */
1413
+ function lintSetFormFieldDeclaredFields(definition, schema) {
1414
+ if (!schema) {
1415
+ return [];
1416
+ }
1417
+ const fieldKeys = new Set((schema.fields ?? []).map((entry) => entry.fieldKey));
1418
+ return readSetFormFieldPaths(definition)
1419
+ .filter(({ reserved }) => reserved.length === 0)
1420
+ .flatMap(({ fieldPath, label }) => {
1421
+ const fieldKey = (0, bpm_core_shared_1.readFormFieldPathTopLevelKey)(fieldPath);
1422
+ return fieldKeys.has(fieldKey)
1423
+ ? []
1424
+ : [
1425
+ (0, bpm_core_shared_1.formatExpressionLintIssue)({
1426
+ code: 'EXPR_FIELD_UNKNOWN',
1427
+ label,
1428
+ severity: 'ERROR',
1429
+ subject: fieldKey,
1430
+ }),
1431
+ ];
1432
+ });
1433
+ }
1146
1434
  function isRecord(value) {
1147
1435
  return typeof value === 'object' && value !== null && !Array.isArray(value);
1148
1436
  }