chore: import upstream snapshot with attribution
Integration Tests - MySQL + Elasticsearch / Detect Changes (push) Has been cancelled
Integration Tests - MySQL + Elasticsearch / integration-tests-mysql-elasticsearch (push) Has been cancelled
Integration Tests - PostgreSQL + Elasticsearch + Redis / Detect Changes (push) Has been cancelled
Integration Tests - PostgreSQL + Elasticsearch + Redis / integration-tests-postgres-elasticsearch-redis (push) Has been cancelled
Integration Tests - PostgreSQL + OpenSearch / Detect Changes (push) Has been cancelled
Integration Tests - PostgreSQL + OpenSearch / integration-tests-postgres-opensearch (push) Has been cancelled
Java Checkstyle / java-checkstyle (push) Has been cancelled
Maven Collate Tests / maven-collate-ci (push) Has been cancelled
OpenMetadata Service Unit Tests / openmetadata-service-unit-tests-status (push) Has been cancelled
Publish Package to Maven Central Repository / publish-maven-packages (push) Has been cancelled
OpenMetadata Service Unit Tests / Detect Changes (push) Has been cancelled
OpenMetadata Service Unit Tests / openmetadata-service-unit-tests (push) Has been cancelled
OpenMetadata Service Unit Tests / k8s_operator-unit-tests (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:35:45 +08:00
commit bf2343b7e4
16049 changed files with 3531137 additions and 0 deletions
@@ -0,0 +1,110 @@
## ADDED Requirements
### Requirement: TCRS event broadcasting via EntityLifecycleEventDispatcher
After `storeInternal()` persists a `TestCaseResolutionStatus` record, the system SHALL broadcast the event via `EntityLifecycleEventDispatcher` to registered handlers. `WorkflowHandler` SHALL receive the event and broadcast a Flowable signal.
#### Scenario: TCRS record creation triggers signal broadcast
- **WHEN** `storeInternal()` persists a new TCRS record (any status: New, Ack, Assigned, Resolved)
- **THEN** `EntityLifecycleEventDispatcher` SHALL notify registered handlers
- **AND** `WorkflowHandler` SHALL broadcast a Flowable signal `"tcrs_{testCaseFQN}"` with process variables including `status`, `testCaseFQN`, `stateId`, and `entityLink`
#### Scenario: Signal starts new process instances
- **WHEN** signal `"tcrs_{testCaseFQN}"` is broadcast
- **AND** a workflow definition has a SignalStartEvent matching that signal
- **THEN** Flowable SHALL create a new process instance with the signal variables as process variables
#### Scenario: Multiple workflows can react to same event
- **WHEN** signal `"tcrs_{testCaseFQN}"` is broadcast
- **AND** multiple workflow definitions have matching SignalStartEvents
- **THEN** each workflow SHALL independently start a new process instance
### Requirement: Default incident lifecycle workflow ships enabled
A default governance workflow definition (`IncidentLifecycleWorkflow.json`) SHALL be bootstrapped on server startup. It SHALL trigger on TCRS events via signal `"tcrs_{testCaseFQN}"`, and its flow SHALL branch on status: non-Resolved events route to `openTask`, Resolved events route to `closeTask`. Auto-assign is NOT enabled by default.
#### Scenario: Workflow is loaded on server startup
- **WHEN** the OpenMetadata server starts
- **THEN** the `IncidentLifecycleWorkflow.json` file in `json/data/governance/workflows/` SHALL be loaded by `initSeedDataFromResources()` with `createOrUpdate` semantics
- **AND** the workflow definition SHALL be available via the governance workflows API
#### Scenario: New failure creates Thread/task via openTask branch
- **WHEN** a TCRS record with status `New` is persisted (test case failure)
- **THEN** the signal starts a new process instance
- **AND** the gateway routes to the `openTask` node (status != Resolved)
- **AND** `openTask` creates a Thread/task with `TaskType.RequestTestCaseFailureResolution`
- **AND** the process ends (short-lived)
#### Scenario: Ack/Assigned event is no-op via openTask branch
- **WHEN** a TCRS record with status `Ack` or `Assigned` is persisted
- **THEN** the signal starts a new process instance
- **AND** the gateway routes to the `openTask` node (status != Resolved)
- **AND** `openTask` detects the Thread/task already exists and is a no-op
- **AND** the process ends (short-lived)
#### Scenario: Resolved event closes Thread/task via closeTask branch
- **WHEN** a TCRS record with status `Resolved` is persisted
- **THEN** the signal starts a new process instance
- **AND** the gateway routes to the `closeTask` node (status == Resolved)
- **AND** `closeTask` closes the open Thread/task
- **AND** the process ends (short-lived)
#### Scenario: Re-open from Resolved creates new lifecycle
- **WHEN** a TCRS record with any non-Resolved status is persisted after a previous Resolved record for the same incident
- **THEN** the signal starts a new process instance
- **AND** the gateway routes to the `openTask` node (status != Resolved)
- **AND** `openTask` detects no open Thread/task exists (previous was closed) and creates a new one
- **AND** the process ends (short-lived)
### Requirement: All processes are short-lived (Slice 1)
In Slice 1, all workflow process instances SHALL complete within the same synchronous call. No process SHALL enter a wait state or persist Flowable runtime rows between events.
#### Scenario: Process completes before API returns
- **WHEN** a TCRS event triggers a workflow process
- **THEN** the process SHALL execute all nodes and reach an end event before the signal broadcast call returns
- **AND** no `ACT_RU_EXECUTION` rows SHALL persist for the process after completion
### Requirement: Ack case backward compatibility
The `Ack` case in `openOrAssignTask()` SHALL check for an existing Thread/task before creating one. If the workflow already created a task, the Ack SHALL patch the assignee on the existing task instead of creating a duplicate.
#### Scenario: Ack with existing workflow-created task patches assignee
- **WHEN** a user Acknowledges an incident
- **AND** `getIncidentTask()` returns an existing Thread (created by the workflow's openTask)
- **THEN** `openOrAssignTask()` SHALL call `patchTaskAssignee()` on the existing Thread with the acknowledging user
- **AND** SHALL NOT call `createTask()`
#### Scenario: Ack without existing task creates task (fallback)
- **WHEN** a user Acknowledges an incident
- **AND** `getIncidentTask()` returns null (workflow did not run, e.g., Flowable was down)
- **THEN** `openOrAssignTask()` SHALL call `createTask()` as before
- **AND** incident lifecycle SHALL continue normally
### Requirement: Severity inference remains in repository
Severity inference SHALL continue to be called inside `storeInternal()` during incident record creation. The workflow does NOT duplicate severity inference — it relies on the severity already being set on the record when it reads it.
#### Scenario: Severity is inferred before workflow sees the record
- **WHEN** `getOrCreateIncident()` creates a new `TestCaseResolutionStatus` record
- **THEN** `storeInternal()` SHALL call `inferIncidentSeverity()` before persisting
- **AND** the record SHALL have severity set when `openTask` reads it to create the Thread/task
### Requirement: incidentId linking remains synchronous
The `TestCaseResolutionStatus` record creation (for `incidentId` linking) SHALL remain synchronous in `getOrCreateIncident()`. The workflow handles Thread/task lifecycle synchronously via the event dispatcher (same thread) after the record is persisted.
#### Scenario: Test result links to incident synchronously
- **WHEN** `setTestCaseResultIncidentId()` is called for a failing test result
- **THEN** `getOrCreateIncident()` SHALL create the `TestCaseResolutionStatus` record synchronously
- **AND** the `stateId` SHALL be available for linking to the test result within the same transaction
- **AND** the Thread/task creation SHALL happen via the workflow triggered by the event broadcast (still within the same synchronous call)
### Requirement: Thread/task lifecycle moves out of repository
The `New` case in `storeInternal()` SHALL no longer create Thread/tasks directly. The `resolveTask()` method SHALL no longer close Thread/tasks directly. These responsibilities move to the workflow's `openTask` and `closeTask` nodes respectively.
#### Scenario: storeInternal New case does not create Thread
- **WHEN** `storeInternal()` handles a `New` TCRS record
- **THEN** it SHALL persist the record and infer severity (unchanged)
- **AND** it SHALL broadcast the event via EntityLifecycleEventDispatcher
- **AND** it SHALL NOT create a Thread/task directly (the workflow's openTask handles this)
#### Scenario: resolveTask does not close Thread directly
- **WHEN** `resolveTask()` resolves an incident
- **THEN** it SHALL persist the Resolved record
- **AND** it SHALL broadcast the event via EntityLifecycleEventDispatcher
- **AND** the workflow's closeTask SHALL handle Thread closure
@@ -0,0 +1,90 @@
## 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.