## ADDED Requirements ### Requirement: openTask node — idempotent Thread/task creation The `openTask` node (`nodeType: automatedTask`, `nodeSubType: openTask`) SHALL compile into a Flowable ServiceTask referencing `OpenTaskDelegate`. It SHALL idempotently create a Thread with configurable `TaskType` and `TaskStatus.Open`. If an open Thread/task already exists for the entity, it SHALL be a no-op. #### Scenario: BPMN structure is compiled correctly - **WHEN** a workflow definition contains an `openTask` node with config `{ "template": "incident", "taskType": "RequestTestCaseFailureResolution" }` - **THEN** the compiled BPMN model SHALL contain a ServiceTask referencing `OpenTaskDelegate.class.getName()` - **AND** the ServiceTask SHALL have Expression fields for `templateExpr`, `taskTypeExpr`, `responsiblesExpr`, and `inputNamespaceMapExpr` #### Scenario: Node registered in NodeFactory - **WHEN** `NodeFactory` receives a node definition with `nodeSubType: openTask` - **THEN** it SHALL instantiate `OpenTask` and return a valid node ### Requirement: OpenTaskDelegate wires Flowable to business logic The `OpenTaskDelegate` SHALL implement `JavaDelegate`, receive configuration via Flowable `Expression` fields, extract values from the `DelegateExecution`, instantiate `OpenTaskImpl`, call it, and set the `taskCreated` process variable from the result. On business logic failure, it SHALL throw a `BpmnError`. #### Scenario: Delegate passes config to impl and sets process variable - **WHEN** `OpenTaskDelegate.execute()` is called with a `DelegateExecution` containing valid expression values - **THEN** it SHALL instantiate `OpenTaskImpl` with resolved config values, call the impl, and set `execution.setVariable("taskCreated", result)` #### Scenario: Delegate wraps exceptions as BpmnError - **WHEN** `OpenTaskImpl` throws an exception during execution - **THEN** `OpenTaskDelegate` SHALL catch it and throw a `BpmnError` ### Requirement: OpenTaskImpl creates Thread/task (clean state) When no open Thread/task exists for the entity, `OpenTaskImpl` SHALL create a Thread with the configured `TaskType` and `TaskStatus.Open` and return `taskCreated = true`. Auto-assignment is configurable via the optional `responsibles` config field. When `responsibles` is not set, the task SHALL be created unassigned (matching current behavior). #### Scenario: First failure creates unassigned Thread (default, no responsibles config) - **WHEN** no open Thread/task exists for the entity - **AND** no `responsibles` config is set on the node - **THEN** `OpenTaskImpl` SHALL create an unassigned Thread task and return `taskCreated = true` #### Scenario: First failure auto-assigns to table owner (responsibles configured) - **WHEN** no open Thread/task exists for the entity - **AND** `responsibles.source` is `"tableOwner"` - **THEN** `OpenTaskImpl` SHALL create a Thread task assigned to the table entity's owner and return `taskCreated = true` #### Scenario: First failure auto-assigns to specific user (responsibles configured) - **WHEN** no open Thread/task exists for the entity - **AND** `responsibles.source` is `"specificUser"` with `responsibles.target` set to a user FQN - **THEN** `OpenTaskImpl` SHALL create a Thread task assigned to the specified user and return `taskCreated = true` ### Requirement: OpenTaskImpl skips when Thread/task already exists When an open Thread/task already exists for the entity, `OpenTaskImpl` SHALL skip creation and return `taskCreated = false`. #### Scenario: Duplicate event is idempotently skipped - **WHEN** an open Thread/task already exists for the entity (e.g., Ack or Assigned event after Thread was created on New) - **THEN** `OpenTaskImpl` SHALL return `taskCreated = false` - **AND** no duplicate Thread SHALL be created ### Requirement: OpenTaskImpl runs as governance-bot All actions performed by `OpenTaskImpl` (Thread creation, task assignment) SHALL execute under the `governance-bot` identity. #### Scenario: Governance-bot identity prevents event loops - **WHEN** `OpenTaskImpl` creates a Thread task - **THEN** the ChangeEvent generated by that creation SHALL have `governance-bot` as the updatedBy user - **AND** `WorkflowEventConsumer` SHALL skip processing that event ### Requirement: closeTask node — Thread/task closure The `closeTask` node (`nodeType: automatedTask`, `nodeSubType: closeTask`) SHALL compile into a Flowable ServiceTask referencing `CloseTaskDelegate`. It SHALL close an open Thread/task for the entity. If no open Thread/task exists, it SHALL be a no-op. #### Scenario: BPMN structure is compiled correctly - **WHEN** a workflow definition contains a `closeTask` node with config `{ "template": "incident", "taskType": "RequestTestCaseFailureResolution" }` - **THEN** the compiled BPMN model SHALL contain a ServiceTask referencing `CloseTaskDelegate.class.getName()` #### Scenario: Node registered in NodeFactory - **WHEN** `NodeFactory` receives a node definition with `nodeSubType: closeTask` - **THEN** it SHALL instantiate `CloseTask` and return a valid node ### Requirement: CloseTaskDelegate wires Flowable to business logic The `CloseTaskDelegate` SHALL implement `JavaDelegate`, receive configuration via Flowable `Expression` fields, instantiate `CloseTaskImpl`, call it, and set the `taskClosed` process variable. #### Scenario: Delegate passes config to impl and sets process variable - **WHEN** `CloseTaskDelegate.execute()` is called with a `DelegateExecution` - **THEN** it SHALL instantiate `CloseTaskImpl`, call the impl, and set `execution.setVariable("taskClosed", result)` ### Requirement: CloseTaskImpl closes Thread/task When an open Thread/task exists for the entity, `CloseTaskImpl` SHALL close it and return `taskClosed = true`. When no open Thread/task exists, it SHALL return `taskClosed = false`. #### Scenario: Open Thread/task is closed - **WHEN** an open Thread/task exists for the entity - **THEN** `CloseTaskImpl` SHALL close the Thread task and return `taskClosed = true` #### Scenario: No-op when no open Thread/task exists - **WHEN** no open Thread/task exists for the entity - **THEN** `CloseTaskImpl` SHALL return `taskClosed = false` without error ### Requirement: CloseTaskImpl runs as governance-bot All actions performed by `CloseTaskImpl` SHALL execute under the `governance-bot` identity.