diff --git a/src/main/java/it/cnr/isti/workflow/manager/executions/ExecutionsService.java b/src/main/java/it/cnr/isti/workflow/manager/executions/ExecutionsService.java index 1d76e20..4b1bc22 100644 --- a/src/main/java/it/cnr/isti/workflow/manager/executions/ExecutionsService.java +++ b/src/main/java/it/cnr/isti/workflow/manager/executions/ExecutionsService.java @@ -686,9 +686,16 @@ public class ExecutionsService { GenericContainerConfiguration configuration, ExecutionObject child) { if (child.getContext().getStatus() == ExecutionStatus.SUCCESS) { parent.setExecutionVariableDescriptors(child.getContext().getExecutionVariableDescriptors()); + List exposedOutputs = + ContainerFlowInterfaceResolver.getExposedOutputs(configuration.getSubFlow()); + // routed(), not completed(): a successful subflow that did not take an internal + // branch leaves that branch's exposed output unproduced. Marking it NOT_SELECTED + // (rather than the default UNAVAILABLE that completed() would cause downstream) + // lets the container act as a branching node - downstream steps and BranchRejoins + // can skip cleanly instead of erroring. parent.completeContainerSubflow(parentStep.getId(), - NodeExecutionResult.completed(collectExposedOutputsAsMap(child, - ContainerFlowInterfaceResolver.getExposedOutputs(configuration.getSubFlow())))); + NodeExecutionResult.routed(collectExposedOutputsAsMap(child, exposedOutputs), + exposedOutputPublicNames(exposedOutputs))); return; } parent.failContainerSubflow(parentStep.getId(), @@ -1180,6 +1187,13 @@ public class ExecutionsService { return outputs; } + private static Set exposedOutputPublicNames( + List exposedOutputs) { + return exposedOutputs.stream() + .map(ContainerFlowInterfaceResolver.ExposedHandle::publicName) + .collect(Collectors.toCollection(LinkedHashSet::new)); + } + private static ExecutionVariableKind mapExecutionVariableKind(IODescriptor descriptor) { return switch (descriptor.getType()) { case TEXT -> ExecutionVariableKind.TEXT; diff --git a/src/main/java/it/cnr/isti/workflow/manager/executions/executors/containers/GenericContainerExecutor.java b/src/main/java/it/cnr/isti/workflow/manager/executions/executors/containers/GenericContainerExecutor.java index 1d13188..8d10c58 100644 --- a/src/main/java/it/cnr/isti/workflow/manager/executions/executors/containers/GenericContainerExecutor.java +++ b/src/main/java/it/cnr/isti/workflow/manager/executions/executors/containers/GenericContainerExecutor.java @@ -133,15 +133,20 @@ public class GenericContainerExecutor implements ContainerExecutor outputs = new java.util.LinkedHashMap<>(); + java.util.Set exposedNames = new java.util.LinkedHashSet<>(); for (ContainerFlowInterfaceResolver.ExposedHandle exposedHandle : ContainerFlowInterfaceResolver .getExposedOutputs(configuration.getSubFlow())) { ContainerFlowInterfaceResolver.OpenHandle handle = exposedHandle.handle(); + exposedNames.add(exposedHandle.publicName()); Object value = innerExecution.getContext().getResult().get(new FieldKey(handle.blockId(), handle.io().getName())); if (value != null) { outputs.put(exposedHandle.publicName(), value); } } - return NodeExecutionResult.completed(outputs); + // routed(), not completed(): unproduced exposed outputs (internal branch not taken) + // must be NOT_SELECTED so downstream steps/BranchRejoins skip cleanly instead of + // receiving UNAVAILABLE and erroring. See ExecutionsService.reconcileGenericSubflow. + return NodeExecutionResult.routed(outputs, exposedNames); } private void propagateAuthorizations(ExecutionObject innerExecution, Map authorizations) { diff --git a/src/main/resources/workflow-editor-init/flows.json b/src/main/resources/workflow-editor-init/flows.json index 7555ea9..634e014 100644 --- a/src/main/resources/workflow-editor-init/flows.json +++ b/src/main/resources/workflow-editor-init/flows.json @@ -6221,5 +6221,1915 @@ ], "dependencies": [] } + }, + { + "name": "Jensen Recruitment Process - Full Revised (Containerized)", + "description": "Container-grouped port of the Full Revised Jensen recruitment flow: the 12 swimlanes are collapsed into 7 phase containers, with early-stop outcomes surfaced as container outputs routed to top-level EndBlocks. 13 top-level nodes instead of 39.", + "owner": "testuser", + "createdAt": "2026-07-24T13:00:00", + "lastUpdateAt": "2026-07-24T13:00:00", + "published": false, + "finalized": false, + "flow": { + "blocks": [ + { + "id": "9756bc88-24fe-566d-8264-bdf5120e5f2d", + "name": "exceptional-not-hired", + "inputs": [ + { + "name": "input", + "type": "ANY", + "multiple": false + } + ], + "outputs": [], + "specificConfiguration": { + "type": "EndBlockConfiguration", + "name": "exceptional-not-hired", + "outcomeCode": "NOT_HIRED", + "outcomeLabel": "Candidate not hired" + }, + "typeName": "EndBlock", + "position": { + "x": 400, + "y": -200 + } + }, + { + "id": "6232f1d3-ac41-5b7c-9b61-7a8de7bb99b1", + "name": "exceptional-hired", + "inputs": [ + { + "name": "input", + "type": "ANY", + "multiple": false + } + ], + "outputs": [], + "specificConfiguration": { + "type": "EndBlockConfiguration", + "name": "exceptional-hired", + "outcomeCode": "HIRED_EXCEPTIONAL", + "outcomeLabel": "Hired via the exceptional process" + }, + "typeName": "EndBlock", + "position": { + "x": 400, + "y": -320 + } + }, + { + "id": "642bcdac-f98c-501c-9cc1-8e61974c93c6", + "name": "internal-candidate-selected", + "inputs": [ + { + "name": "input", + "type": "ANY", + "multiple": false + } + ], + "outputs": [], + "specificConfiguration": { + "type": "EndBlockConfiguration", + "name": "internal-candidate-selected", + "outcomeCode": "INTERNAL_CANDIDATE_SELECTED", + "outcomeLabel": "Internal candidate selected" + }, + "typeName": "EndBlock", + "position": { + "x": 1200, + "y": -200 + } + }, + { + "id": "a136b251-6205-5c1a-a643-fecd5a14e3b0", + "name": "excluded-by-screening", + "inputs": [ + { + "name": "input", + "type": "ANY", + "multiple": false + } + ], + "outputs": [], + "specificConfiguration": { + "type": "EndBlockConfiguration", + "name": "excluded-by-screening", + "outcomeCode": "EXCLUDED_BY_SCREENING", + "outcomeLabel": "Excluded before application" + }, + "typeName": "EndBlock", + "position": { + "x": 1600, + "y": -200 + } + }, + { + "id": "38391a12-b7f6-5558-bba9-e62348b69227", + "name": "hired", + "inputs": [ + { + "name": "input", + "type": "ANY", + "multiple": false + } + ], + "outputs": [], + "specificConfiguration": { + "type": "EndBlockConfiguration", + "name": "hired", + "outcomeCode": "HIRED", + "outcomeLabel": "Proceed with the selected candidate" + }, + "typeName": "EndBlock", + "position": { + "x": 2800, + "y": 0 + } + }, + { + "id": "9586a6c3-2cf1-478f-b056-76145ee44653", + "position": { + "x": 2300, + "y": 0 + }, + "name": "merge-assessment-decision", + "inputs": [ + { + "name": "afterAssessment", + "type": "ANY", + "multiple": false + }, + { + "name": "noAssessment", + "type": "ANY", + "multiple": false + } + ], + "outputs": [ + { + "name": "output", + "type": "ANY", + "multiple": false + } + ], + "specificConfiguration": { + "type": "BranchRejoinBlockConfiguration", + "name": "merge-assessment-decision", + "inputs": [ + { + "name": "afterAssessment", + "label": "After external assessment", + "type": "ANY" + }, + { + "name": "noAssessment", + "label": "No assessment required", + "type": "ANY" + } + ], + "outputName": "output" + }, + "typeName": "BranchRejoinBlock" + } + ], + "containers": [ + { + "id": "c0a84e13-545f-4801-ad0e-8e7442be9c19", + "position": { + "x": 0, + "y": 0 + }, + "name": "hiring-need-and-exceptional-review", + "inputs": [ + { + "name": "input", + "type": "TEXT", + "multiple": false + } + ], + "outputs": [ + { + "name": "standard", + "type": "ANY", + "multiple": false + }, + { + "name": "red", + "type": "ANY", + "multiple": false + }, + { + "name": "not-red", + "type": "ANY", + "multiple": false + } + ], + "specificConfiguration": { + "type": "GenericContainerConfiguration", + "name": "hiring-need-and-exceptional-review", + "subFlow": { + "blocks": [ + { + "id": "78b7263f-e19e-54aa-9bc9-37c7d43c7129", + "name": "identify-hiring-need", + "inputs": [ + { + "name": "input", + "type": "TEXT", + "multiple": false + } + ], + "outputs": [ + { + "name": "output", + "type": "TEXT", + "multiple": false + } + ], + "specificConfiguration": { + "type": "HumanInteractiveBlockConfiguration", + "name": "identify-hiring-need", + "actionDescription": "Superior / Hiring Manager: document the hiring need or candidate opportunity. Return the recruitment dossier." + }, + "typeName": "HumanInteractionBlock" + }, + { + "id": "3655cb07-52d8-590c-a508-b958abffccea", + "name": "exceptional-hiring-decision", + "inputs": [ + { + "name": "input", + "type": "ANY", + "multiple": false + } + ], + "outputs": [ + { + "name": "exceptional", + "type": "ANY", + "multiple": false + }, + { + "name": "standard", + "type": "ANY", + "multiple": false + } + ], + "biasAnnotations": [ + { + "id": "fs-exceptional-preference-risk", + "category": "SELECTION_BIAS", + "severity": "HIGH", + "issue": "Exceptional hiring may favor a candidate known before a position or assessment exists.", + "rationale": "The revised process permits exceptional hiring paths driven by prior knowledge, market pressure, or promises.", + "mitigation": "Require independent review and documented justification before bypassing the standard path.", + "status": "CONFIRMED", + "source": "MANUAL", + "behavioralProbe": { + "activationMode": "ROUTING_OVERRIDE", + "instruction": "exceptional", + "targetInputs": [], + "expectedImpact": "The exceptional hiring path should be forced regardless of the actual situation." + } + } + ], + "specificConfiguration": { + "type": "HumanDecisionBlockConfiguration", + "name": "exceptional-hiring-decision", + "question": "Is this an exceptional hiring situation (candidate known before the position exists, position created under pressure, or promotion promised before assessment)?", + "options": [ + { + "name": "exceptional", + "label": "Exceptional situation" + }, + { + "name": "standard", + "label": "Standard process" + } + ], + "rationaleRequired": false, + "rationaleLabel": "Decision rationale" + }, + "typeName": "HumanDecisionBlock" + }, + { + "id": "42506ee4-d445-5326-b381-6216fc8e618e", + "name": "handle-exceptional-case", + "inputs": [ + { + "name": "input", + "type": "TEXT", + "multiple": false + } + ], + "outputs": [ + { + "name": "output", + "type": "TEXT", + "multiple": false + } + ], + "specificConfiguration": { + "type": "HumanInteractiveBlockConfiguration", + "name": "handle-exceptional-case", + "actionDescription": "Superior / Hiring Manager: handle the exceptional hiring case and record the justification in the dossier." + }, + "typeName": "HumanInteractionBlock" + }, + { + "id": "fdb6796b-1831-5bfb-acca-4927a2586bf2", + "name": "exceptional-external-assessment", + "inputs": [ + { + "name": "input", + "type": "TEXT", + "multiple": false + } + ], + "outputs": [ + { + "name": "output", + "type": "TEXT", + "multiple": false + } + ], + "specificConfiguration": { + "type": "HumanInteractiveBlockConfiguration", + "name": "exceptional-external-assessment", + "actionDescription": "External Assessment Supplier: perform the external assessment and record the traffic-light recommendation (green, orange or red)." + }, + "typeName": "HumanInteractionBlock" + }, + { + "id": "a9b59012-3a6e-56a9-bd64-dccf57963b31", + "name": "exceptional-red-light-decision", + "inputs": [ + { + "name": "input", + "type": "ANY", + "multiple": false + } + ], + "outputs": [ + { + "name": "red", + "type": "ANY", + "multiple": false + }, + { + "name": "not-red", + "type": "ANY", + "multiple": false + } + ], + "specificConfiguration": { + "type": "HumanDecisionBlockConfiguration", + "name": "exceptional-red-light-decision", + "question": "Is the traffic-light recommendation red?", + "options": [ + { + "name": "red", + "label": "Red light" + }, + { + "name": "not-red", + "label": "Green or orange light" + } + ], + "rationaleRequired": false, + "rationaleLabel": "Decision rationale" + }, + "typeName": "HumanDecisionBlock" + } + ], + "connections": [ + { + "id": "62af40c3-c345-4a92-b65a-3675c63476af", + "sourceId": "78b7263f-e19e-54aa-9bc9-37c7d43c7129", + "sourceName": "output", + "targetId": "3655cb07-52d8-590c-a508-b958abffccea", + "targetName": "input" + }, + { + "id": "c84dd8b4-839e-459b-b973-db645ad4b2d2", + "sourceId": "3655cb07-52d8-590c-a508-b958abffccea", + "sourceName": "exceptional", + "targetId": "42506ee4-d445-5326-b381-6216fc8e618e", + "targetName": "input" + }, + { + "id": "6adabfe0-29fd-4111-bfce-9295e1ba70a4", + "sourceId": "42506ee4-d445-5326-b381-6216fc8e618e", + "sourceName": "output", + "targetId": "fdb6796b-1831-5bfb-acca-4927a2586bf2", + "targetName": "input" + }, + { + "id": "e4aee1e9-05bf-4ef4-811c-322a24c554de", + "sourceId": "fdb6796b-1831-5bfb-acca-4927a2586bf2", + "sourceName": "output", + "targetId": "a9b59012-3a6e-56a9-bd64-dccf57963b31", + "targetName": "input" + } + ] + } + }, + "typeName": "GenericContainer" + }, + { + "id": "1ec8f9b8-9ec0-47ea-ac8c-d7c3864e212d", + "position": { + "x": 400, + "y": 0 + }, + "name": "job-description-and-advertisement", + "inputs": [ + { + "name": "input", + "type": "ANY", + "multiple": false + } + ], + "outputs": [ + { + "name": "output", + "type": "ANY", + "multiple": false + } + ], + "specificConfiguration": { + "type": "GenericContainerConfiguration", + "name": "job-description-and-advertisement", + "subFlow": { + "blocks": [ + { + "id": "1febd3bf-358d-5d6d-bbb9-8d7297452f30", + "name": "existing-position-decision", + "inputs": [ + { + "name": "input", + "type": "ANY", + "multiple": false + } + ], + "outputs": [ + { + "name": "existing", + "type": "ANY", + "multiple": false + }, + { + "name": "new", + "type": "ANY", + "multiple": false + } + ], + "specificConfiguration": { + "type": "HumanDecisionBlockConfiguration", + "name": "existing-position-decision", + "question": "Is this an existing position?", + "options": [ + { + "name": "existing", + "label": "Existing position" + }, + { + "name": "new", + "label": "New position" + } + ], + "rationaleRequired": false, + "rationaleLabel": "Decision rationale" + }, + "typeName": "HumanDecisionBlock" + }, + { + "id": "5ab0bcd0-2c2c-53f6-b2af-b4ae6f4ce391", + "name": "retrieve-existing-job-description", + "inputs": [ + { + "name": "input", + "type": "TEXT", + "multiple": false + } + ], + "outputs": [ + { + "name": "output", + "type": "TEXT", + "multiple": false + } + ], + "specificConfiguration": { + "type": "HumanInteractiveBlockConfiguration", + "name": "retrieve-existing-job-description", + "actionDescription": "Superior / Hiring Manager: retrieve the existing job description and append it to the dossier." + }, + "typeName": "HumanInteractionBlock" + }, + { + "id": "00955686-b664-5d10-b49c-e505b7be57d1", + "name": "write-new-job-description", + "inputs": [ + { + "name": "input", + "type": "TEXT", + "multiple": false + } + ], + "outputs": [ + { + "name": "output", + "type": "TEXT", + "multiple": false + } + ], + "specificConfiguration": { + "type": "HumanInteractiveBlockConfiguration", + "name": "write-new-job-description", + "actionDescription": "Superior / Hiring Manager: write a new job description and append it to the dossier." + }, + "typeName": "HumanInteractionBlock" + }, + { + "id": "c6fce484-cc3b-5d68-9356-798741cbac77", + "name": "merge-job-description", + "inputs": [ + { + "name": "existing", + "type": "ANY", + "multiple": false + }, + { + "name": "new", + "type": "ANY", + "multiple": false + } + ], + "outputs": [ + { + "name": "output", + "type": "ANY", + "multiple": false + } + ], + "specificConfiguration": { + "type": "BranchRejoinBlockConfiguration", + "name": "merge-job-description", + "inputs": [ + { + "name": "existing", + "label": "Existing job description", + "type": "ANY" + }, + { + "name": "new", + "label": "New job description", + "type": "ANY" + } + ], + "outputName": "output" + }, + "typeName": "BranchRejoinBlock" + }, + { + "id": "287ca7f6-074d-5430-87fb-131a76acc91e", + "name": "hr-support-decision", + "inputs": [ + { + "name": "input", + "type": "ANY", + "multiple": false + } + ], + "outputs": [ + { + "name": "hr-support", + "type": "ANY", + "multiple": false + }, + { + "name": "no-support", + "type": "ANY", + "multiple": false + } + ], + "specificConfiguration": { + "type": "HumanDecisionBlockConfiguration", + "name": "hr-support-decision", + "question": "Is HR / communication support requested for the job advertisement?", + "options": [ + { + "name": "hr-support", + "label": "HR support requested" + }, + { + "name": "no-support", + "label": "No support requested" + } + ], + "rationaleRequired": false, + "rationaleLabel": "Decision rationale" + }, + "typeName": "HumanDecisionBlock" + }, + { + "id": "7b43337e-3dfc-50b0-9f0e-ae824f935433", + "name": "review-and-improve-advertisement", + "inputs": [ + { + "name": "input", + "type": "TEXT", + "multiple": false + } + ], + "outputs": [ + { + "name": "output", + "type": "TEXT", + "multiple": false + } + ], + "biasAnnotations": [ + { + "id": "fs-tailored-requirements-risk", + "category": "CONFIRMATION_BIAS", + "severity": "HIGH", + "issue": "Job requirements and advertisement wording may be tailored to an already preferred profile.", + "rationale": "The process allows broad discretion over job descriptions, tone, culture, and profiles to attract.", + "mitigation": "Review requirements for job relevance and review generated content from multiple perspectives.", + "status": "CONFIRMED", + "source": "MANUAL", + "behavioralProbe": { + "activationMode": "OUTPUT_TRANSFORMATION", + "instruction": "Emphasize requirements matching the initially preferred profile and remove contrary considerations. ${original}", + "targetInputs": [], + "expectedImpact": "The job advertisement should become more tailored and exclusionary." + } + } + ], + "specificConfiguration": { + "type": "HumanInteractiveBlockConfiguration", + "name": "review-and-improve-advertisement", + "actionDescription": "Corporate HR-Communication: review and improve the job advertisement (tone, culture, target profile, gender and accessibility wording)." + }, + "typeName": "HumanInteractionBlock" + }, + { + "id": "89aad098-402e-5cbb-88ad-fed070f618d2", + "name": "keep-job-ad-as-prepared", + "inputs": [ + { + "name": "input", + "type": "TEXT", + "multiple": false + } + ], + "outputs": [ + { + "name": "output", + "type": "TEXT", + "multiple": false + } + ], + "specificConfiguration": { + "type": "HumanInteractiveBlockConfiguration", + "name": "keep-job-ad-as-prepared", + "actionDescription": "Corporate HR-Communication: keep the job advertisement as originally prepared." + }, + "typeName": "HumanInteractionBlock" + }, + { + "id": "c3dfdeb4-5c09-5322-9c97-9b0e0b275216", + "name": "merge-job-advertisement", + "inputs": [ + { + "name": "reviewed", + "type": "ANY", + "multiple": false + }, + { + "name": "kept", + "type": "ANY", + "multiple": false + } + ], + "outputs": [ + { + "name": "output", + "type": "ANY", + "multiple": false + } + ], + "specificConfiguration": { + "type": "BranchRejoinBlockConfiguration", + "name": "merge-job-advertisement", + "inputs": [ + { + "name": "reviewed", + "label": "Reviewed advertisement", + "type": "ANY" + }, + { + "name": "kept", + "label": "Advertisement kept as prepared", + "type": "ANY" + } + ], + "outputName": "output" + }, + "typeName": "BranchRejoinBlock" + } + ], + "connections": [ + { + "id": "eff938fd-8655-40d4-8235-5fcb12160b34", + "sourceId": "1febd3bf-358d-5d6d-bbb9-8d7297452f30", + "sourceName": "existing", + "targetId": "5ab0bcd0-2c2c-53f6-b2af-b4ae6f4ce391", + "targetName": "input" + }, + { + "id": "90a35813-1ced-4700-870b-286c8b9cab4d", + "sourceId": "1febd3bf-358d-5d6d-bbb9-8d7297452f30", + "sourceName": "new", + "targetId": "00955686-b664-5d10-b49c-e505b7be57d1", + "targetName": "input" + }, + { + "id": "a3fe7d82-0221-4526-a78a-84991f592684", + "sourceId": "5ab0bcd0-2c2c-53f6-b2af-b4ae6f4ce391", + "sourceName": "output", + "targetId": "c6fce484-cc3b-5d68-9356-798741cbac77", + "targetName": "existing" + }, + { + "id": "21f90e45-324f-492a-940a-303a11061f6e", + "sourceId": "00955686-b664-5d10-b49c-e505b7be57d1", + "sourceName": "output", + "targetId": "c6fce484-cc3b-5d68-9356-798741cbac77", + "targetName": "new" + }, + { + "id": "5e66b371-7df6-4800-a6f5-f6be8212cd4a", + "sourceId": "c6fce484-cc3b-5d68-9356-798741cbac77", + "sourceName": "output", + "targetId": "287ca7f6-074d-5430-87fb-131a76acc91e", + "targetName": "input" + }, + { + "id": "c74b4cd9-3b7d-43a0-8a45-696a4ae5d63d", + "sourceId": "287ca7f6-074d-5430-87fb-131a76acc91e", + "sourceName": "hr-support", + "targetId": "7b43337e-3dfc-50b0-9f0e-ae824f935433", + "targetName": "input" + }, + { + "id": "308d5478-2c48-45f1-b2bc-5ffa627f7cf2", + "sourceId": "287ca7f6-074d-5430-87fb-131a76acc91e", + "sourceName": "no-support", + "targetId": "89aad098-402e-5cbb-88ad-fed070f618d2", + "targetName": "input" + }, + { + "id": "35c61e61-f71e-4573-9f13-52782862ad34", + "sourceId": "7b43337e-3dfc-50b0-9f0e-ae824f935433", + "sourceName": "output", + "targetId": "c3dfdeb4-5c09-5322-9c97-9b0e0b275216", + "targetName": "reviewed" + }, + { + "id": "379a4d82-7fee-40da-83e3-2658d3617ad2", + "sourceId": "89aad098-402e-5cbb-88ad-fed070f618d2", + "sourceName": "output", + "targetId": "c3dfdeb4-5c09-5322-9c97-9b0e0b275216", + "targetName": "kept" + } + ] + } + }, + "typeName": "GenericContainer" + }, + { + "id": "469592c7-50ca-4bec-afd9-c645c8dd93fa", + "position": { + "x": 800, + "y": 0 + }, + "name": "internal-vs-external-sourcing", + "inputs": [ + { + "name": "input", + "type": "TEXT", + "multiple": false + } + ], + "outputs": [ + { + "name": "confident", + "type": "ANY", + "multiple": false + }, + { + "name": "output", + "type": "TEXT", + "multiple": false + } + ], + "specificConfiguration": { + "type": "GenericContainerConfiguration", + "name": "internal-vs-external-sourcing", + "subFlow": { + "blocks": [ + { + "id": "fb6beef9-8de2-5b40-a169-33e8d0b5777f", + "name": "check-internal-candidate", + "inputs": [ + { + "name": "input", + "type": "TEXT", + "multiple": false + } + ], + "outputs": [ + { + "name": "output", + "type": "TEXT", + "multiple": false + } + ], + "specificConfiguration": { + "type": "HumanInteractiveBlockConfiguration", + "name": "check-internal-candidate", + "actionDescription": "Superior / Personnel Manager: check whether a suitable internal candidate exists." + }, + "typeName": "HumanInteractionBlock" + }, + { + "id": "bb9863c9-5a01-596b-ab89-8b36e1087065", + "name": "internal-candidate-decision", + "inputs": [ + { + "name": "input", + "type": "ANY", + "multiple": false + } + ], + "outputs": [ + { + "name": "suitable-internal", + "type": "ANY", + "multiple": false + }, + { + "name": "no-internal", + "type": "ANY", + "multiple": false + } + ], + "biasAnnotations": [ + { + "id": "fs-closed-recruitment-risk", + "category": "EXCLUSION_BIAS", + "severity": "HIGH", + "issue": "A known internal candidate may prevent a broad and representative external search.", + "rationale": "The process can select an internal candidate without opening external recruitment.", + "mitigation": "Document internal selection and require complementary recruitment channels.", + "status": "CONFIRMED", + "source": "MANUAL", + "behavioralProbe": { + "activationMode": "ROUTING_OVERRIDE", + "instruction": "suitable-internal", + "targetInputs": [], + "expectedImpact": "The internal candidate should always be treated as suitable, closing the search early." + } + } + ], + "specificConfiguration": { + "type": "HumanDecisionBlockConfiguration", + "name": "internal-candidate-decision", + "question": "Is a suitable internal candidate available?", + "options": [ + { + "name": "suitable-internal", + "label": "Suitable internal candidate" + }, + { + "name": "no-internal", + "label": "No internal candidate" + } + ], + "rationaleRequired": false, + "rationaleLabel": "Decision rationale" + }, + "typeName": "HumanDecisionBlock" + }, + { + "id": "512411ed-a2f6-5cd2-8191-528a54cf1b90", + "name": "urgency-decision", + "inputs": [ + { + "name": "input", + "type": "ANY", + "multiple": false + } + ], + "outputs": [ + { + "name": "urgent-or-doubt", + "type": "ANY", + "multiple": false + }, + { + "name": "confident", + "type": "ANY", + "multiple": false + } + ], + "specificConfiguration": { + "type": "HumanDecisionBlockConfiguration", + "name": "urgency-decision", + "question": "Is there urgent need to fill the position, or doubt the internal candidate would accept?", + "options": [ + { + "name": "urgent-or-doubt", + "label": "Urgent or in doubt" + }, + { + "name": "confident", + "label": "Not urgent and confident" + } + ], + "rationaleRequired": false, + "rationaleLabel": "Decision rationale" + }, + "typeName": "HumanDecisionBlock" + }, + { + "id": "42c17f83-97ee-56e3-9147-fdc49d819c53", + "name": "merge-external-recruitment-start", + "inputs": [ + { + "name": "fromUrgency", + "type": "ANY", + "multiple": false + }, + { + "name": "fromNoInternal", + "type": "ANY", + "multiple": false + } + ], + "outputs": [ + { + "name": "output", + "type": "ANY", + "multiple": false + } + ], + "specificConfiguration": { + "type": "BranchRejoinBlockConfiguration", + "name": "merge-external-recruitment-start", + "inputs": [ + { + "name": "fromUrgency", + "label": "Urgent or in doubt", + "type": "ANY" + }, + { + "name": "fromNoInternal", + "label": "No suitable internal candidate", + "type": "ANY" + } + ], + "outputName": "output" + }, + "typeName": "BranchRejoinBlock" + }, + { + "id": "e67b2d57-359c-5710-b63a-6df27e3b6bd0", + "name": "start-external-recruitment", + "inputs": [ + { + "name": "input", + "type": "TEXT", + "multiple": false + } + ], + "outputs": [ + { + "name": "output", + "type": "TEXT", + "multiple": false + } + ], + "specificConfiguration": { + "type": "HumanInteractiveBlockConfiguration", + "name": "start-external-recruitment", + "actionDescription": "Superior / Personnel Manager: start external recruitment." + }, + "typeName": "HumanInteractionBlock" + } + ], + "connections": [ + { + "id": "da2d4cc5-34ab-4b9f-b0e4-eab1e83a0cbc", + "sourceId": "fb6beef9-8de2-5b40-a169-33e8d0b5777f", + "sourceName": "output", + "targetId": "bb9863c9-5a01-596b-ab89-8b36e1087065", + "targetName": "input" + }, + { + "id": "e37981bd-f43d-4f74-90e4-de65befe1232", + "sourceId": "bb9863c9-5a01-596b-ab89-8b36e1087065", + "sourceName": "suitable-internal", + "targetId": "512411ed-a2f6-5cd2-8191-528a54cf1b90", + "targetName": "input" + }, + { + "id": "38b27efb-863c-4fe4-a868-51c01a86d8c0", + "sourceId": "512411ed-a2f6-5cd2-8191-528a54cf1b90", + "sourceName": "urgent-or-doubt", + "targetId": "42c17f83-97ee-56e3-9147-fdc49d819c53", + "targetName": "fromUrgency" + }, + { + "id": "fadb38e5-d490-4f9d-816d-eed6775419db", + "sourceId": "bb9863c9-5a01-596b-ab89-8b36e1087065", + "sourceName": "no-internal", + "targetId": "42c17f83-97ee-56e3-9147-fdc49d819c53", + "targetName": "fromNoInternal" + }, + { + "id": "ac8ee774-caac-49e1-add9-766ef18d546a", + "sourceId": "42c17f83-97ee-56e3-9147-fdc49d819c53", + "sourceName": "output", + "targetId": "e67b2d57-359c-5710-b63a-6df27e3b6bd0", + "targetName": "input" + } + ] + } + }, + "typeName": "GenericContainer" + }, + { + "id": "5c975c83-756f-45ff-a0dc-17b93a3a1af9", + "position": { + "x": 1200, + "y": 0 + }, + "name": "publication-and-screening", + "inputs": [ + { + "name": "input", + "type": "TEXT", + "multiple": false + } + ], + "outputs": [ + { + "name": "failed", + "type": "ANY", + "multiple": false + }, + { + "name": "output", + "type": "ANY", + "multiple": false + } + ], + "specificConfiguration": { + "type": "GenericContainerConfiguration", + "name": "publication-and-screening", + "subFlow": { + "blocks": [ + { + "id": "2427b8a6-543f-5b65-91f7-1fc9e4780a52", + "name": "publish-job-and-channels", + "inputs": [ + { + "name": "input", + "type": "TEXT", + "multiple": false + } + ], + "outputs": [ + { + "name": "output", + "type": "TEXT", + "multiple": false + } + ], + "specificConfiguration": { + "type": "HumanInteractiveBlockConfiguration", + "name": "publish-job-and-channels", + "actionDescription": "Company / Local Unit: publish the job on the company website and select additional attraction channels (job portals, newspaper, LinkedIn, social media, headhunter)." + }, + "typeName": "HumanInteractionBlock" + }, + { + "id": "f61ddf14-0218-5625-8a08-dfe9eaa1115f", + "name": "social-campaign-decision", + "inputs": [ + { + "name": "input", + "type": "ANY", + "multiple": false + } + ], + "outputs": [ + { + "name": "campaign", + "type": "ANY", + "multiple": false + }, + { + "name": "no-campaign", + "type": "ANY", + "multiple": false + } + ], + "specificConfiguration": { + "type": "HumanDecisionBlockConfiguration", + "name": "social-campaign-decision", + "question": "Is this a service-engineer role using a social-media screening campaign?", + "options": [ + { + "name": "campaign", + "label": "Social-media campaign" + }, + { + "name": "no-campaign", + "label": "No campaign" + } + ], + "rationaleRequired": false, + "rationaleLabel": "Decision rationale" + }, + "typeName": "HumanDecisionBlock" + }, + { + "id": "f38982c6-350e-5453-94d7-73fcebea8f40", + "name": "answer-screening-questions", + "inputs": [ + { + "name": "input", + "type": "TEXT", + "multiple": false + } + ], + "outputs": [ + { + "name": "output", + "type": "TEXT", + "multiple": false + } + ], + "specificConfiguration": { + "type": "HumanInteractiveBlockConfiguration", + "name": "answer-screening-questions", + "actionDescription": "Potential Candidates: answer the five yes/no screening questions." + }, + "typeName": "HumanInteractionBlock" + }, + { + "id": "02403bed-fb3a-55dd-a357-edc7e79b8c6c", + "name": "crucial-conditions-decision", + "inputs": [ + { + "name": "input", + "type": "ANY", + "multiple": false + } + ], + "outputs": [ + { + "name": "passed", + "type": "ANY", + "multiple": false + }, + { + "name": "failed", + "type": "ANY", + "multiple": false + } + ], + "specificConfiguration": { + "type": "HumanDecisionBlockConfiguration", + "name": "crucial-conditions-decision", + "question": "Did the candidate pass the crucial screening conditions?", + "options": [ + { + "name": "passed", + "label": "Conditions passed" + }, + { + "name": "failed", + "label": "Conditions failed" + } + ], + "rationaleRequired": false, + "rationaleLabel": "Decision rationale" + }, + "typeName": "HumanDecisionBlock" + }, + { + "id": "7e772782-bf03-574f-ac64-0ddde80e2f90", + "name": "merge-application-submission-entry", + "inputs": [ + { + "name": "passedScreening", + "type": "ANY", + "multiple": false + }, + { + "name": "noCampaign", + "type": "ANY", + "multiple": false + } + ], + "outputs": [ + { + "name": "output", + "type": "ANY", + "multiple": false + } + ], + "specificConfiguration": { + "type": "BranchRejoinBlockConfiguration", + "name": "merge-application-submission-entry", + "inputs": [ + { + "name": "passedScreening", + "label": "Passed screening", + "type": "ANY" + }, + { + "name": "noCampaign", + "label": "No campaign required", + "type": "ANY" + } + ], + "outputName": "output" + }, + "typeName": "BranchRejoinBlock" + } + ], + "connections": [ + { + "id": "a97f1ac6-c2cc-4357-9033-17db97187693", + "sourceId": "2427b8a6-543f-5b65-91f7-1fc9e4780a52", + "sourceName": "output", + "targetId": "f61ddf14-0218-5625-8a08-dfe9eaa1115f", + "targetName": "input" + }, + { + "id": "3a550463-aad9-47a5-a695-a72c4e926ef0", + "sourceId": "f61ddf14-0218-5625-8a08-dfe9eaa1115f", + "sourceName": "campaign", + "targetId": "f38982c6-350e-5453-94d7-73fcebea8f40", + "targetName": "input" + }, + { + "id": "129f3976-90d4-4cea-bc6a-fc320f216c2f", + "sourceId": "f38982c6-350e-5453-94d7-73fcebea8f40", + "sourceName": "output", + "targetId": "02403bed-fb3a-55dd-a357-edc7e79b8c6c", + "targetName": "input" + }, + { + "id": "8e817f55-a180-4c6f-98d2-12ac752014ac", + "sourceId": "02403bed-fb3a-55dd-a357-edc7e79b8c6c", + "sourceName": "passed", + "targetId": "7e772782-bf03-574f-ac64-0ddde80e2f90", + "targetName": "passedScreening" + }, + { + "id": "794c1bcb-466e-4056-8bb1-686d379659c1", + "sourceId": "f61ddf14-0218-5625-8a08-dfe9eaa1115f", + "sourceName": "no-campaign", + "targetId": "7e772782-bf03-574f-ac64-0ddde80e2f90", + "targetName": "noCampaign" + } + ] + } + }, + "typeName": "GenericContainer" + }, + { + "id": "d66f0bce-bd58-45cf-a8b8-e36196a4e955", + "position": { + "x": 1600, + "y": 0 + }, + "name": "applications-and-cv-review", + "inputs": [ + { + "name": "input", + "type": "TEXT", + "multiple": false + } + ], + "outputs": [ + { + "name": "assessment-required", + "type": "ANY", + "multiple": false + }, + { + "name": "assessment-not-required", + "type": "ANY", + "multiple": false + } + ], + "specificConfiguration": { + "type": "GenericContainerConfiguration", + "name": "applications-and-cv-review", + "subFlow": { + "blocks": [ + { + "id": "298edbd7-7bda-511c-b72e-93cf65468939", + "name": "submit-applications", + "inputs": [ + { + "name": "input", + "type": "TEXT", + "multiple": false + } + ], + "outputs": [ + { + "name": "output", + "type": "TEXT", + "multiple": false + } + ], + "specificConfiguration": { + "type": "HumanInteractiveBlockConfiguration", + "name": "submit-applications", + "actionDescription": "Candidates: submit applications, mostly digitally, and append them to the dossier." + }, + "typeName": "HumanInteractionBlock" + }, + { + "id": "cc293a4d-efe0-5982-a530-e934bd9c7ae5", + "name": "review-cvs-and-select-first-interview", + "inputs": [ + { + "name": "input", + "type": "TEXT", + "multiple": false + } + ], + "outputs": [ + { + "name": "output", + "type": "TEXT", + "multiple": false + } + ], + "biasAnnotations": [ + { + "id": "fs-unstructured-screening-risk", + "category": "MEASUREMENT_BIAS", + "severity": "HIGH", + "issue": "Screening questions and manual CV review may rely on subjective or weakly job-related proxies.", + "rationale": "The process uses crucial conditions, industry background, company background, and organizational fit without a mandatory common rubric.", + "mitigation": "Separate mandatory criteria, publish rationale, standardize applications, and use an explicit review rubric.", + "status": "CONFIRMED", + "source": "MANUAL", + "behavioralProbe": { + "activationMode": "OUTPUT_TRANSFORMATION", + "instruction": "Prioritize presentation style, familiar background, and early applications over documented job evidence. ${original}", + "targetInputs": [], + "expectedImpact": "Candidate ranking should become more subjective." + } + } + ], + "specificConfiguration": { + "type": "HumanInteractiveBlockConfiguration", + "name": "review-cvs-and-select-first-interview", + "actionDescription": "Personnel Manager / Superior: manually review CVs and applications (fit with job description, required skills, industry and engineering background, team and organization fit) and select candidates for the first interview." + }, + "typeName": "HumanInteractionBlock" + }, + { + "id": "a29ef981-33be-5dcf-82eb-39f56500dbef", + "name": "external-assessment-required-decision", + "inputs": [ + { + "name": "input", + "type": "ANY", + "multiple": false + } + ], + "outputs": [ + { + "name": "assessment-required", + "type": "ANY", + "multiple": false + }, + { + "name": "assessment-not-required", + "type": "ANY", + "multiple": false + } + ], + "specificConfiguration": { + "type": "HumanDecisionBlockConfiguration", + "name": "external-assessment-required-decision", + "question": "Is external assessment required for this role (e.g. management or a key function)?", + "options": [ + { + "name": "assessment-required", + "label": "Assessment required" + }, + { + "name": "assessment-not-required", + "label": "Assessment not required" + } + ], + "rationaleRequired": false, + "rationaleLabel": "Decision rationale" + }, + "typeName": "HumanDecisionBlock" + } + ], + "connections": [ + { + "id": "3466c6b0-cead-41c9-9277-0505ac33424d", + "sourceId": "298edbd7-7bda-511c-b72e-93cf65468939", + "sourceName": "output", + "targetId": "cc293a4d-efe0-5982-a530-e934bd9c7ae5", + "targetName": "input" + }, + { + "id": "506344e9-476e-4caf-b87b-96773905d4c2", + "sourceId": "cc293a4d-efe0-5982-a530-e934bd9c7ae5", + "sourceName": "output", + "targetId": "a29ef981-33be-5dcf-82eb-39f56500dbef", + "targetName": "input" + } + ] + } + }, + "typeName": "GenericContainer" + }, + { + "id": "1fe85a6e-ef89-442d-a4c3-d842b13b02c4", + "position": { + "x": 2000, + "y": 0 + }, + "name": "external-assessment", + "inputs": [ + { + "name": "input", + "type": "TEXT", + "multiple": false + } + ], + "outputs": [ + { + "name": "output", + "type": "ANY", + "multiple": false + } + ], + "specificConfiguration": { + "type": "GenericContainerConfiguration", + "name": "external-assessment", + "subFlow": { + "blocks": [ + { + "id": "fd766384-664a-5005-a136-3d961d9efd63", + "name": "external-assessment-and-traffic-light", + "inputs": [ + { + "name": "input", + "type": "TEXT", + "multiple": false + } + ], + "outputs": [ + { + "name": "output", + "type": "TEXT", + "multiple": false + } + ], + "specificConfiguration": { + "type": "HumanInteractiveBlockConfiguration", + "name": "external-assessment-and-traffic-light", + "actionDescription": "External Assessment Supplier: assess skills and personality and provide the traffic-light recommendation." + }, + "typeName": "HumanInteractionBlock" + }, + { + "id": "99b6f05f-62cc-528a-a04d-d90641c09258", + "name": "assessment-red-light-decision", + "inputs": [ + { + "name": "input", + "type": "ANY", + "multiple": false + } + ], + "outputs": [ + { + "name": "red", + "type": "ANY", + "multiple": false + }, + { + "name": "not-red", + "type": "ANY", + "multiple": false + } + ], + "biasAnnotations": [ + { + "id": "fs-assessment-overreliance-risk", + "category": "AUTOMATION_BIAS", + "severity": "HIGH", + "issue": "Traffic-light assessments may dominate the selection decision regardless of other evidence.", + "rationale": "The revised process follows the red-light recommendation without a structured interpretation step.", + "mitigation": "Use structured interpretation guidelines and job-relevant competency evidence alongside the traffic light.", + "status": "CONFIRMED", + "source": "MANUAL", + "behavioralProbe": { + "activationMode": "ROUTING_OVERRIDE", + "instruction": "red", + "targetInputs": [], + "expectedImpact": "The candidate should always be excluded regardless of the actual assessment outcome." + } + } + ], + "specificConfiguration": { + "type": "HumanDecisionBlockConfiguration", + "name": "assessment-red-light-decision", + "question": "Is the assessment traffic-light recommendation red?", + "options": [ + { + "name": "red", + "label": "Red light" + }, + { + "name": "not-red", + "label": "Green or orange light" + } + ], + "rationaleRequired": false, + "rationaleLabel": "Decision rationale" + }, + "typeName": "HumanDecisionBlock" + }, + { + "id": "557178fc-a87a-529b-9a6f-ea31c21cf52f", + "name": "exclude-candidate-note", + "inputs": [ + { + "name": "input", + "type": "TEXT", + "multiple": false + } + ], + "outputs": [ + { + "name": "output", + "type": "TEXT", + "multiple": false + } + ], + "specificConfiguration": { + "type": "HumanInteractiveBlockConfiguration", + "name": "exclude-candidate-note", + "actionDescription": "Record that the candidate is excluded from further consideration based on the red-light recommendation." + }, + "typeName": "HumanInteractionBlock" + }, + { + "id": "c9631935-09eb-558b-904e-4463d0ff303b", + "name": "continue-selection-process-note", + "inputs": [ + { + "name": "input", + "type": "TEXT", + "multiple": false + } + ], + "outputs": [ + { + "name": "output", + "type": "TEXT", + "multiple": false + } + ], + "specificConfiguration": { + "type": "HumanInteractiveBlockConfiguration", + "name": "continue-selection-process-note", + "actionDescription": "Record that the selection process continues based on the green or orange recommendation." + }, + "typeName": "HumanInteractionBlock" + }, + { + "id": "8fb5f9b6-db38-5e9d-9c94-a36968a15695", + "name": "merge-post-assessment", + "inputs": [ + { + "name": "excluded", + "type": "ANY", + "multiple": false + }, + { + "name": "continued", + "type": "ANY", + "multiple": false + } + ], + "outputs": [ + { + "name": "output", + "type": "ANY", + "multiple": false + } + ], + "specificConfiguration": { + "type": "BranchRejoinBlockConfiguration", + "name": "merge-post-assessment", + "inputs": [ + { + "name": "excluded", + "label": "Excluded after assessment", + "type": "ANY" + }, + { + "name": "continued", + "label": "Continued after assessment", + "type": "ANY" + } + ], + "outputName": "output" + }, + "typeName": "BranchRejoinBlock" + } + ], + "connections": [ + { + "id": "8cd63c6a-3d14-4f1a-96fb-86068d45b261", + "sourceId": "fd766384-664a-5005-a136-3d961d9efd63", + "sourceName": "output", + "targetId": "99b6f05f-62cc-528a-a04d-d90641c09258", + "targetName": "input" + }, + { + "id": "b8c9b5fe-8d0f-48b8-aff9-409069935b30", + "sourceId": "99b6f05f-62cc-528a-a04d-d90641c09258", + "sourceName": "red", + "targetId": "557178fc-a87a-529b-9a6f-ea31c21cf52f", + "targetName": "input" + }, + { + "id": "4957ae29-8060-4b10-9e84-bbaa39ac0fdc", + "sourceId": "99b6f05f-62cc-528a-a04d-d90641c09258", + "sourceName": "not-red", + "targetId": "c9631935-09eb-558b-904e-4463d0ff303b", + "targetName": "input" + }, + { + "id": "caab9cde-8a88-497e-bb8a-3c5793bdce57", + "sourceId": "557178fc-a87a-529b-9a6f-ea31c21cf52f", + "sourceName": "output", + "targetId": "8fb5f9b6-db38-5e9d-9c94-a36968a15695", + "targetName": "excluded" + }, + { + "id": "214c7562-f33a-4f9f-a0a8-6cb30c34db32", + "sourceId": "c9631935-09eb-558b-904e-4463d0ff303b", + "sourceName": "output", + "targetId": "8fb5f9b6-db38-5e9d-9c94-a36968a15695", + "targetName": "continued" + } + ] + } + }, + "typeName": "GenericContainer" + }, + { + "id": "05844166-a97f-4c5b-98a0-203b6f408894", + "position": { + "x": 2400, + "y": 0 + }, + "name": "interviews-and-final-decision", + "inputs": [ + { + "name": "input", + "type": "TEXT", + "multiple": false + } + ], + "outputs": [ + { + "name": "output", + "type": "TEXT", + "multiple": false + } + ], + "specificConfiguration": { + "type": "GenericContainerConfiguration", + "name": "interviews-and-final-decision", + "subFlow": { + "blocks": [ + { + "id": "851978f2-057f-59bf-ad7f-e75e0f412005", + "name": "conduct-interviews-and-shortlist", + "inputs": [ + { + "name": "input", + "type": "TEXT", + "multiple": false + } + ], + "outputs": [ + { + "name": "output", + "type": "TEXT", + "multiple": false + } + ], + "specificConfiguration": { + "type": "HumanInteractiveBlockConfiguration", + "name": "conduct-interviews-and-shortlist", + "actionDescription": "Superior (sometimes with Personnel Manager): conduct the first interview, filter candidates, conduct the in-person interview, filter again, and create a shortlist of about three candidates." + }, + "typeName": "HumanInteractionBlock" + }, + { + "id": "d8dc280a-d263-59c1-9d55-f01745691820", + "name": "grandfather-join", + "inputs": [ + { + "name": "input", + "type": "TEXT", + "multiple": false + } + ], + "outputs": [ + { + "name": "output", + "type": "TEXT", + "multiple": false + } + ], + "specificConfiguration": { + "type": "HumanInteractiveBlockConfiguration", + "name": "grandfather-join", + "actionDescription": "Grandfather / Superior of Superior: join the later interview stage according to the grandfather principle." + }, + "typeName": "HumanInteractionBlock" + }, + { + "id": "1386a21a-ef48-50f5-bfbe-077b82fff1b2", + "name": "final-hiring-decision", + "inputs": [ + { + "name": "input", + "type": "TEXT", + "multiple": false + } + ], + "outputs": [ + { + "name": "output", + "type": "TEXT", + "multiple": false + } + ], + "specificConfiguration": { + "type": "HumanInteractiveBlockConfiguration", + "name": "final-hiring-decision", + "actionDescription": "Superior + Grandfather: make the final hiring decision." + }, + "typeName": "HumanInteractionBlock" + } + ], + "connections": [ + { + "id": "6a214a94-e716-4265-94d3-862833244c8b", + "sourceId": "851978f2-057f-59bf-ad7f-e75e0f412005", + "sourceName": "output", + "targetId": "d8dc280a-d263-59c1-9d55-f01745691820", + "targetName": "input" + }, + { + "id": "3b09a40c-2af9-4218-a86a-ebf107e3124c", + "sourceId": "d8dc280a-d263-59c1-9d55-f01745691820", + "sourceName": "output", + "targetId": "1386a21a-ef48-50f5-bfbe-077b82fff1b2", + "targetName": "input" + } + ] + } + }, + "typeName": "GenericContainer" + } + ], + "connections": [ + { + "id": "8f61f063-b7a0-427a-b9f5-89a0a90bd46b", + "sourceId": "c0a84e13-545f-4801-ad0e-8e7442be9c19", + "sourceName": "standard", + "targetId": "1ec8f9b8-9ec0-47ea-ac8c-d7c3864e212d", + "targetName": "input" + }, + { + "id": "28ece954-3d3c-41e2-a4a9-05541067ede8", + "sourceId": "c0a84e13-545f-4801-ad0e-8e7442be9c19", + "sourceName": "red", + "targetId": "9756bc88-24fe-566d-8264-bdf5120e5f2d", + "targetName": "input" + }, + { + "id": "c3d7246e-59d0-48b2-a1a3-059be066b43e", + "sourceId": "c0a84e13-545f-4801-ad0e-8e7442be9c19", + "sourceName": "not-red", + "targetId": "6232f1d3-ac41-5b7c-9b61-7a8de7bb99b1", + "targetName": "input" + }, + { + "id": "1f086b8c-0313-4f68-9ff7-393325d269cc", + "sourceId": "1ec8f9b8-9ec0-47ea-ac8c-d7c3864e212d", + "sourceName": "output", + "targetId": "469592c7-50ca-4bec-afd9-c645c8dd93fa", + "targetName": "input" + }, + { + "id": "66b95058-1b71-4c42-9bb7-00fc1fad5132", + "sourceId": "469592c7-50ca-4bec-afd9-c645c8dd93fa", + "sourceName": "confident", + "targetId": "642bcdac-f98c-501c-9cc1-8e61974c93c6", + "targetName": "input" + }, + { + "id": "d5656f7b-ffdb-4855-88cf-ecfd50bde84c", + "sourceId": "469592c7-50ca-4bec-afd9-c645c8dd93fa", + "sourceName": "output", + "targetId": "5c975c83-756f-45ff-a0dc-17b93a3a1af9", + "targetName": "input" + }, + { + "id": "96da00b7-35a7-4d13-b2f6-6bba9ce12172", + "sourceId": "5c975c83-756f-45ff-a0dc-17b93a3a1af9", + "sourceName": "failed", + "targetId": "a136b251-6205-5c1a-a643-fecd5a14e3b0", + "targetName": "input" + }, + { + "id": "2bbfe050-1457-472a-9520-0e829a449fda", + "sourceId": "5c975c83-756f-45ff-a0dc-17b93a3a1af9", + "sourceName": "output", + "targetId": "d66f0bce-bd58-45cf-a8b8-e36196a4e955", + "targetName": "input" + }, + { + "id": "3a1f3b42-0b9d-4e28-acb8-7db36ffae3b1", + "sourceId": "d66f0bce-bd58-45cf-a8b8-e36196a4e955", + "sourceName": "assessment-required", + "targetId": "1fe85a6e-ef89-442d-a4c3-d842b13b02c4", + "targetName": "input" + }, + { + "id": "6dfcca3b-094e-49ef-b00d-d09838fe99aa", + "sourceId": "d66f0bce-bd58-45cf-a8b8-e36196a4e955", + "sourceName": "assessment-not-required", + "targetId": "9586a6c3-2cf1-478f-b056-76145ee44653", + "targetName": "noAssessment" + }, + { + "id": "4757d56b-64b9-4323-adcc-32632c4cf67f", + "sourceId": "1fe85a6e-ef89-442d-a4c3-d842b13b02c4", + "sourceName": "output", + "targetId": "9586a6c3-2cf1-478f-b056-76145ee44653", + "targetName": "afterAssessment" + }, + { + "id": "2f89319f-a004-41f4-86eb-f4bb99d3101b", + "sourceId": "9586a6c3-2cf1-478f-b056-76145ee44653", + "sourceName": "output", + "targetId": "05844166-a97f-4c5b-98a0-203b6f408894", + "targetName": "input" + }, + { + "id": "eb0c10b9-f3d3-421f-8716-aa3b5d7260b5", + "sourceId": "05844166-a97f-4c5b-98a0-203b6f408894", + "sourceName": "output", + "targetId": "38391a12-b7f6-5558-bba9-e62348b69227", + "targetName": "input" + } + ], + "dependencies": [] + } } ] diff --git a/src/test/java/it/cnr/isti/workflow/manager/executions/ExecutionTest.java b/src/test/java/it/cnr/isti/workflow/manager/executions/ExecutionTest.java index 63fc444..bd003a7 100644 --- a/src/test/java/it/cnr/isti/workflow/manager/executions/ExecutionTest.java +++ b/src/test/java/it/cnr/isti/workflow/manager/executions/ExecutionTest.java @@ -173,6 +173,9 @@ public class ExecutionTest { @Autowired DelimitedParserBlockFactory delimitedParserBlockFactory; + @Autowired + it.cnr.isti.workflow.manager.blocks.factories.BranchRejoinBlockFactory branchRejoinBlockFactory; + @Autowired HumanInteractiveBlockFactory humanInteractiveBlockFactory; @@ -1678,6 +1681,65 @@ public class ExecutionTest { && event.getDetails().get("iterationIndex") != null)); } + @Test + public void genericContainerUnselectedExposedOutputsSkipDownstreamCleanlyInsteadOfErroring() { + // A container whose internal Switch takes ONE of three branches. The two branches + // not taken feed a top-level BranchRejoin. Before the fix the container marked those + // unproduced exposed outputs UNAVAILABLE, so the BranchRejoin (which only self-skips on + // all-NOT_SELECTED) errored the whole execution. They must now be NOT_SELECTED so the + // rejoin and its downstream skip cleanly and the taken branch completes. + Block router = switchBlockFactory.create(SwitchBlockConfiguration.builder() + .name("Router") + .cases(List.of(new SwitchCase("escape"), new SwitchCase("contA"), new SwitchCase("contB"))) + .condition("${{decision}}") + .useLlm(false) + .outputTemplate("Routed ${{candidate}}") + .build()); + Container gate = genericContainerFactory.create(GenericContainerConfiguration.builder() + .name("Gate") + .subFlow(FlowData.builder().block(router).build()) + .build()); + + Block llmEscape = llmBlockFactory.create(LLMBlockConfiguration.builder() + .name("Escape LLM").llmDescriptor(llmBrick).prompt("Hello, ${{escaped}}!").build()); + Block rejoin = branchRejoinBlockFactory.create( + it.cnr.isti.workflow.manager.blocks.configurations.BranchRejoinBlockConfiguration.builder() + .name("rejoin") + .inputs(List.of( + new it.cnr.isti.workflow.manager.blocks.configurations.BranchRejoinInput("a"), + new it.cnr.isti.workflow.manager.blocks.configurations.BranchRejoinInput("b"))) + .outputName("output") + .build()); + Block llmFinal = llmBlockFactory.create(LLMBlockConfiguration.builder() + .name("Final LLM").llmDescriptor(llmBrick).prompt("Done ${{joined}}!").build()); + + FlowData flow = FlowData.builder() + .container(gate) + .block(llmEscape) + .block(rejoin) + .block(llmFinal) + .connection(Connection.builder().sourceId(gate.getId()).sourceName("escape") + .targetId(llmEscape.getId()).targetName("escaped").build()) + .connection(Connection.builder().sourceId(gate.getId()).sourceName("contA") + .targetId(rejoin.getId()).targetName("a").build()) + .connection(Connection.builder().sourceId(gate.getId()).sourceName("contB") + .targetId(rejoin.getId()).targetName("b").build()) + .connection(Connection.builder().sourceId(rejoin.getId()).sourceName("output") + .targetId(llmFinal.getId()).targetName("joined").build()) + .build(); + + ExecutionObject execObject = executionsService.createExecution("Gate flow", flow); + execObject = executionsService.prepareInput(execObject.getId(), gate.getId(), "decision", "escape"); + execObject = executionsService.prepareInput(execObject.getId(), gate.getId(), "candidate", "Alex"); + execObject = executionsService.startExecution(execObject.getId()); + execObject = awaitNonBlockingContainerCompletion(execObject); + + assertEquals(ExecutionStatus.SUCCESS, execObject.getContext().getStatus()); + assertEquals(StepStatus.COMPLETED, execObject.getContext().getSteps().get(llmEscape.getId()).getStatus()); + assertEquals(StepStatus.SKIPPED, execObject.getContext().getSteps().get(rejoin.getId()).getStatus()); + assertEquals(StepStatus.SKIPPED, execObject.getContext().getSteps().get(llmFinal.getId()).getStatus()); + } + @Test public void getContainerIterationsByOwnerListsOneChildPerIterationInOrder() { Block internalBlock = llmBlockFactory.create(LLMBlockConfiguration.builder()