Validé sur PC Windows (DESKTOP-58D5CAC, 2560x1600) : - 8 clics résolus visuellement (1 anchor_template, 1 som_text_match, 6 som_vlm) - Score moyen 0.75, temps moyen 1.6s - Texte tapé correctement (bonjour, test word, date, email) - 0 retries, 2 actions non vérifiées (OK) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
616 lines
22 KiB
Markdown
616 lines
22 KiB
Markdown
# Design Document - Workflow Composition
|
|
|
|
## Overview
|
|
|
|
Ce document décrit l'architecture et la conception pour la composition et le contrôle de flux des workflows dans RPA Vision V3. Le système permet de chaîner des workflows, créer des sous-workflows réutilisables, fusionner des workflows similaires, et utiliser des structures de contrôle (boucles, conditions, déclencheurs).
|
|
|
|
## Architecture
|
|
|
|
```mermaid
|
|
graph TB
|
|
subgraph "Composition Layer"
|
|
WC[WorkflowChainer]
|
|
SR[SubWorkflowRegistry]
|
|
WM[WorkflowMerger]
|
|
SE[SequenceExtractor]
|
|
end
|
|
|
|
subgraph "Control Flow Layer"
|
|
LE[LoopExecutor]
|
|
CE[ConditionalEvaluator]
|
|
TM[TriggerManager]
|
|
end
|
|
|
|
subgraph "Data Layer"
|
|
VM[VariableManager]
|
|
DG[DependencyGraph]
|
|
EL[ExecutionLogger]
|
|
end
|
|
|
|
subgraph "Existing Core"
|
|
WG[WorkflowGraph]
|
|
EX[ExecutionLoop]
|
|
TR[TargetResolver]
|
|
end
|
|
|
|
WC --> VM
|
|
WC --> EL
|
|
SR --> DG
|
|
WM --> WG
|
|
SE --> SR
|
|
|
|
LE --> EX
|
|
CE --> TR
|
|
TM --> EX
|
|
|
|
VM --> EL
|
|
DG --> SR
|
|
```
|
|
|
|
## Components and Interfaces
|
|
|
|
### 1. WorkflowChainer
|
|
|
|
Gère l'exécution séquentielle de workflows chaînés.
|
|
|
|
```python
|
|
@dataclass
|
|
class ChainConfig:
|
|
"""Configuration de chaînage entre workflows."""
|
|
source_workflow_id: str
|
|
target_workflow_id: str
|
|
variable_mapping: Dict[str, str] # source_var -> target_var
|
|
on_failure: Literal["retry", "skip", "abort"]
|
|
max_retries: int = 3
|
|
|
|
class WorkflowChainer:
|
|
def __init__(self, variable_manager: VariableManager, logger: ExecutionLogger):
|
|
self.variable_manager = variable_manager
|
|
self.logger = logger
|
|
self.chains: Dict[str, List[ChainConfig]] = {}
|
|
|
|
def add_chain(self, config: ChainConfig) -> None:
|
|
"""Ajoute une configuration de chaînage."""
|
|
|
|
def validate_chain(self, source_id: str, target_id: str) -> ValidationResult:
|
|
"""Valide la compatibilité entre deux workflows."""
|
|
|
|
def execute_chain(self, start_workflow_id: str, context: ExecutionContext) -> ChainResult:
|
|
"""Exécute une chaîne de workflows."""
|
|
|
|
def to_dict(self) -> Dict[str, Any]:
|
|
"""Sérialise la configuration de chaînage."""
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: Dict[str, Any], ...) -> 'WorkflowChainer':
|
|
"""Désérialise la configuration de chaînage."""
|
|
```
|
|
|
|
### 2. SubWorkflowRegistry
|
|
|
|
Gère les sous-workflows réutilisables et leurs références.
|
|
|
|
```python
|
|
@dataclass
|
|
class SubWorkflowDefinition:
|
|
"""Définition d'un sous-workflow."""
|
|
workflow_id: str
|
|
name: str
|
|
input_parameters: List[ParameterDef]
|
|
output_values: List[ParameterDef]
|
|
|
|
@dataclass
|
|
class ReferenceNode:
|
|
"""Node qui référence un sous-workflow."""
|
|
node_id: str
|
|
sub_workflow_id: str
|
|
input_bindings: Dict[str, str] # param_name -> variable_name
|
|
output_bindings: Dict[str, str]
|
|
|
|
class SubWorkflowRegistry:
|
|
def __init__(self, dependency_graph: DependencyGraph):
|
|
self.dependency_graph = dependency_graph
|
|
self.definitions: Dict[str, SubWorkflowDefinition] = {}
|
|
self.references: Dict[str, List[ReferenceNode]] = {}
|
|
|
|
def register(self, definition: SubWorkflowDefinition) -> None:
|
|
"""Enregistre un sous-workflow."""
|
|
|
|
def create_reference(self, parent_workflow_id: str, ref: ReferenceNode) -> None:
|
|
"""Crée une référence vers un sous-workflow."""
|
|
|
|
def execute_reference(self, ref: ReferenceNode, context: ExecutionContext) -> ExecutionResult:
|
|
"""Exécute un sous-workflow via sa référence."""
|
|
|
|
def get_dependents(self, workflow_id: str) -> List[str]:
|
|
"""Retourne les workflows qui dépendent de ce sous-workflow."""
|
|
|
|
def to_dict(self) -> Dict[str, Any]:
|
|
"""Sérialise le registre."""
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: Dict[str, Any], ...) -> 'SubWorkflowRegistry':
|
|
"""Désérialise le registre."""
|
|
```
|
|
|
|
### 3. WorkflowMerger
|
|
|
|
Détecte et fusionne les workflows similaires.
|
|
|
|
```python
|
|
@dataclass
|
|
class MergeCandidate:
|
|
"""Candidat à la fusion."""
|
|
workflow_a_id: str
|
|
workflow_b_id: str
|
|
similarity_score: float
|
|
shared_nodes: List[str]
|
|
conflicts: List[NodeConflict]
|
|
|
|
@dataclass
|
|
class NodeConflict:
|
|
"""Conflit entre deux nodes."""
|
|
node_id: str
|
|
action_a: str
|
|
action_b: str
|
|
|
|
class WorkflowMerger:
|
|
def __init__(self, similarity_threshold: float = 0.9):
|
|
self.similarity_threshold = similarity_threshold
|
|
|
|
def calculate_similarity(self, workflow_a: WorkflowGraph, workflow_b: WorkflowGraph) -> float:
|
|
"""Calcule la similarité entre deux workflows."""
|
|
|
|
def find_merge_candidates(self, workflows: List[WorkflowGraph]) -> List[MergeCandidate]:
|
|
"""Trouve les paires de workflows candidats à la fusion."""
|
|
|
|
def merge(self, workflow_a: WorkflowGraph, workflow_b: WorkflowGraph,
|
|
conflict_resolutions: Dict[str, str]) -> WorkflowGraph:
|
|
"""Fusionne deux workflows en préservant tous les chemins uniques."""
|
|
|
|
def get_unique_paths(self, workflow: WorkflowGraph) -> List[List[str]]:
|
|
"""Extrait tous les chemins uniques d'un workflow."""
|
|
```
|
|
|
|
### 4. SequenceExtractor
|
|
|
|
Détecte et extrait les séquences communes.
|
|
|
|
```python
|
|
@dataclass
|
|
class CommonSequence:
|
|
"""Séquence commune détectée."""
|
|
nodes: List[str]
|
|
occurrences: List[SequenceOccurrence]
|
|
priority: Literal["high", "medium", "low"]
|
|
|
|
@dataclass
|
|
class SequenceOccurrence:
|
|
"""Occurrence d'une séquence dans un workflow."""
|
|
workflow_id: str
|
|
start_index: int
|
|
end_index: int
|
|
|
|
class SequenceExtractor:
|
|
def __init__(self, registry: SubWorkflowRegistry, min_sequence_length: int = 3):
|
|
self.registry = registry
|
|
self.min_sequence_length = min_sequence_length
|
|
|
|
def find_common_sequences(self, workflows: List[WorkflowGraph]) -> List[CommonSequence]:
|
|
"""Trouve les séquences communes entre workflows."""
|
|
|
|
def extract_as_subworkflow(self, sequence: CommonSequence, name: str) -> SubWorkflowDefinition:
|
|
"""Extrait une séquence comme sous-workflow."""
|
|
|
|
def replace_with_references(self, sequence: CommonSequence, sub_workflow_id: str) -> None:
|
|
"""Remplace les occurrences par des références."""
|
|
```
|
|
|
|
### 5. LoopExecutor
|
|
|
|
Gère l'exécution des boucles.
|
|
|
|
```python
|
|
@dataclass
|
|
class LoopConfig:
|
|
"""Configuration d'une boucle."""
|
|
loop_id: str
|
|
loop_type: Literal["count", "condition"]
|
|
max_iterations: Optional[int] # Pour type "count"
|
|
exit_condition: Optional[VisualCondition] # Pour type "condition"
|
|
body_nodes: List[str]
|
|
safety_limit: int = 1000
|
|
|
|
@dataclass
|
|
class LoopState:
|
|
"""État d'exécution d'une boucle."""
|
|
loop_id: str
|
|
current_iteration: int
|
|
started_at: datetime
|
|
last_condition_result: Optional[bool]
|
|
|
|
class LoopExecutor:
|
|
def __init__(self, execution_loop: ExecutionLoop, safety_limit: int = 1000):
|
|
self.execution_loop = execution_loop
|
|
self.safety_limit = safety_limit
|
|
self.active_loops: Dict[str, LoopState] = {}
|
|
|
|
def start_loop(self, config: LoopConfig, context: ExecutionContext) -> LoopState:
|
|
"""Démarre une nouvelle boucle."""
|
|
|
|
def execute_iteration(self, loop_id: str, context: ExecutionContext) -> IterationResult:
|
|
"""Exécute une itération de la boucle."""
|
|
|
|
def should_continue(self, loop_id: str, context: ExecutionContext) -> bool:
|
|
"""Évalue si la boucle doit continuer."""
|
|
|
|
def increment_counter(self, loop_id: str) -> int:
|
|
"""Incrémente le compteur et retourne la nouvelle valeur."""
|
|
|
|
def to_dict(self) -> Dict[str, Any]:
|
|
"""Sérialise l'état des boucles."""
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: Dict[str, Any], ...) -> 'LoopExecutor':
|
|
"""Désérialise l'état des boucles."""
|
|
```
|
|
|
|
### 6. ConditionalEvaluator
|
|
|
|
Évalue les conditions et détermine les branches à exécuter.
|
|
|
|
```python
|
|
@dataclass
|
|
class BranchConfig:
|
|
"""Configuration d'une branche conditionnelle."""
|
|
branch_id: str
|
|
condition: VisualCondition
|
|
target_node: str
|
|
priority: int # Ordre d'évaluation
|
|
|
|
@dataclass
|
|
class ConditionalNode:
|
|
"""Node conditionnel avec plusieurs branches."""
|
|
node_id: str
|
|
branches: List[BranchConfig]
|
|
default_branch: Optional[str]
|
|
|
|
@dataclass
|
|
class VisualCondition:
|
|
"""Condition basée sur l'état visuel."""
|
|
condition_type: Literal["element_present", "element_absent", "text_equals", "text_contains"]
|
|
target_element: Optional[str] # ID ou template
|
|
expected_text: Optional[str]
|
|
|
|
class ConditionalEvaluator:
|
|
def __init__(self, target_resolver: TargetResolver):
|
|
self.target_resolver = target_resolver
|
|
|
|
def evaluate_condition(self, condition: VisualCondition, screenshot: np.ndarray) -> bool:
|
|
"""Évalue une condition visuelle."""
|
|
|
|
def evaluate_node(self, node: ConditionalNode, screenshot: np.ndarray) -> str:
|
|
"""Évalue un node conditionnel et retourne l'ID de la branche à exécuter."""
|
|
|
|
def to_dict(self) -> Dict[str, Any]:
|
|
"""Sérialise la configuration."""
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: Dict[str, Any], ...) -> 'ConditionalEvaluator':
|
|
"""Désérialise la configuration."""
|
|
```
|
|
|
|
### 7. TriggerManager
|
|
|
|
Gère les déclencheurs automatiques.
|
|
|
|
```python
|
|
@dataclass
|
|
class ScheduleTrigger:
|
|
"""Déclencheur basé sur un horaire."""
|
|
trigger_id: str
|
|
workflow_id: str
|
|
cron_expression: Optional[str]
|
|
interval_seconds: Optional[int]
|
|
|
|
@dataclass
|
|
class FileTrigger:
|
|
"""Déclencheur basé sur l'apparition d'un fichier."""
|
|
trigger_id: str
|
|
workflow_id: str
|
|
watch_directory: str
|
|
file_pattern: str
|
|
|
|
@dataclass
|
|
class VisualTrigger:
|
|
"""Déclencheur basé sur la détection visuelle."""
|
|
trigger_id: str
|
|
workflow_id: str
|
|
target_element: str
|
|
check_interval_seconds: int
|
|
|
|
@dataclass
|
|
class TriggerContext:
|
|
"""Contexte passé au workflow lors du déclenchement."""
|
|
trigger_id: str
|
|
trigger_type: str
|
|
fired_at: datetime
|
|
file_path: Optional[str]
|
|
detected_element: Optional[Dict[str, Any]]
|
|
|
|
class TriggerManager:
|
|
def __init__(self, execution_loop: ExecutionLoop, concurrency_mode: Literal["concurrent", "queue"]):
|
|
self.execution_loop = execution_loop
|
|
self.concurrency_mode = concurrency_mode
|
|
self.triggers: Dict[str, Union[ScheduleTrigger, FileTrigger, VisualTrigger]] = {}
|
|
self.execution_queue: List[Tuple[str, TriggerContext]] = []
|
|
|
|
def register_trigger(self, trigger: Union[ScheduleTrigger, FileTrigger, VisualTrigger]) -> None:
|
|
"""Enregistre un déclencheur."""
|
|
|
|
def fire_trigger(self, trigger_id: str) -> TriggerContext:
|
|
"""Déclenche manuellement un trigger et retourne le contexte."""
|
|
|
|
def handle_concurrent_triggers(self, workflow_id: str, contexts: List[TriggerContext]) -> None:
|
|
"""Gère les déclenchements concurrents."""
|
|
|
|
def to_dict(self) -> Dict[str, Any]:
|
|
"""Sérialise la configuration des triggers."""
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: Dict[str, Any], ...) -> 'TriggerManager':
|
|
"""Désérialise la configuration des triggers."""
|
|
```
|
|
|
|
### 8. DependencyGraph
|
|
|
|
Gère le graphe de dépendances entre workflows.
|
|
|
|
```python
|
|
class DependencyGraph:
|
|
def __init__(self):
|
|
self.dependencies: Dict[str, Set[str]] = {} # workflow -> sous-workflows utilisés
|
|
self.dependents: Dict[str, Set[str]] = {} # sous-workflow -> workflows qui l'utilisent
|
|
|
|
def add_dependency(self, workflow_id: str, sub_workflow_id: str) -> None:
|
|
"""Ajoute une dépendance."""
|
|
|
|
def remove_dependency(self, workflow_id: str, sub_workflow_id: str) -> None:
|
|
"""Supprime une dépendance."""
|
|
|
|
def get_dependencies(self, workflow_id: str) -> Set[str]:
|
|
"""Retourne les dépendances d'un workflow."""
|
|
|
|
def get_dependents(self, workflow_id: str) -> Set[str]:
|
|
"""Retourne les workflows qui dépendent de celui-ci."""
|
|
|
|
def has_circular_dependency(self, workflow_id: str, sub_workflow_id: str) -> bool:
|
|
"""Vérifie si l'ajout créerait une dépendance circulaire."""
|
|
|
|
def get_usage_stats(self, workflow_id: str) -> Dict[str, int]:
|
|
"""Retourne les statistiques d'utilisation."""
|
|
```
|
|
|
|
### 9. VariableManager (Extension)
|
|
|
|
Extension du VariableManager existant pour les variables globales.
|
|
|
|
```python
|
|
class GlobalVariableManager:
|
|
def __init__(self):
|
|
self.global_vars: Dict[str, Any] = {}
|
|
self.override_log: List[VariableOverride] = []
|
|
|
|
def set_global(self, name: str, value: Any, source_workflow: str) -> None:
|
|
"""Définit une variable globale."""
|
|
|
|
def get_global(self, name: str, default: Any = None) -> Any:
|
|
"""Récupère une variable globale."""
|
|
|
|
def transfer_to_workflow(self, target_workflow_id: str) -> Dict[str, Any]:
|
|
"""Transfère les variables au workflow suivant."""
|
|
|
|
def get_final_state(self) -> Dict[str, Any]:
|
|
"""Retourne l'état final des variables."""
|
|
|
|
def to_dict(self) -> Dict[str, Any]:
|
|
"""Sérialise le contexte d'exécution."""
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: Dict[str, Any]) -> 'GlobalVariableManager':
|
|
"""Désérialise le contexte d'exécution."""
|
|
```
|
|
|
|
## Data Models
|
|
|
|
```python
|
|
@dataclass
|
|
class WorkflowCompositionConfig:
|
|
"""Configuration complète de composition."""
|
|
chains: List[ChainConfig]
|
|
sub_workflows: Dict[str, SubWorkflowDefinition]
|
|
references: Dict[str, List[ReferenceNode]]
|
|
loops: Dict[str, LoopConfig]
|
|
conditionals: Dict[str, ConditionalNode]
|
|
triggers: List[Union[ScheduleTrigger, FileTrigger, VisualTrigger]]
|
|
|
|
@dataclass
|
|
class ExecutionContext:
|
|
"""Contexte d'exécution partagé."""
|
|
chain_id: str
|
|
current_workflow_id: str
|
|
global_variables: Dict[str, Any]
|
|
execution_log: List[LogEntry]
|
|
trigger_context: Optional[TriggerContext]
|
|
|
|
@dataclass
|
|
class LogEntry:
|
|
"""Entrée du log d'exécution unifié."""
|
|
timestamp: datetime
|
|
workflow_id: str
|
|
node_id: str
|
|
event_type: str
|
|
details: Dict[str, Any]
|
|
|
|
@dataclass
|
|
class ValidationResult:
|
|
"""Résultat de validation."""
|
|
is_valid: bool
|
|
errors: List[str]
|
|
warnings: List[str]
|
|
```
|
|
|
|
|
|
## Correctness Properties
|
|
|
|
*A property is a characteristic or behavior that should hold true across all valid executions of a system-essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.*
|
|
|
|
### Property Reflection
|
|
|
|
Après analyse des critères d'acceptation, voici les propriétés consolidées en éliminant les redondances :
|
|
|
|
- Les propriétés de round-trip (1.6, 2.6, 6.6, 7.6, 8.6, 9.4) peuvent être combinées en une seule propriété générique de sérialisation
|
|
- Les propriétés de propagation de variables (1.2, 9.1, 9.5) peuvent être combinées
|
|
- Les propriétés de validation de dépendances (5.2, 5.3) peuvent être combinées
|
|
|
|
### Properties
|
|
|
|
**Property 1: Round-trip de sérialisation**
|
|
*Pour tout* objet de configuration (ChainConfig, LoopConfig, ConditionalNode, Trigger, ExecutionContext), la sérialisation suivie de la désérialisation doit produire un objet équivalent.
|
|
**Validates: Requirements 1.6, 2.6, 6.6, 7.6, 8.6, 9.4**
|
|
|
|
**Property 2: Propagation des variables dans les chaînes**
|
|
*Pour tout* workflow A avec des variables de contexte chaîné vers un workflow B, toutes les variables mappées doivent être accessibles dans B avec leurs valeurs correctes, et en cas de conflit de noms, la valeur la plus récente prévaut.
|
|
**Validates: Requirements 1.2, 9.1, 9.5**
|
|
|
|
**Property 3: Validation de compatibilité de chaînage**
|
|
*Pour toute* paire de workflows (A, B) avec une configuration de chaînage, la validation doit détecter les incompatibilités entre l'état final de A et l'état initial de B.
|
|
**Validates: Requirements 1.4**
|
|
|
|
**Property 4: Log unifié complet**
|
|
*Pour toute* chaîne de workflows exécutée, le log unifié doit contenir exactement tous les événements de tous les workflows dans l'ordre chronologique.
|
|
**Validates: Requirements 1.5**
|
|
|
|
**Property 5: Retour de contrôle après sous-workflow**
|
|
*Pour tout* workflow parent appelant un sous-workflow via une référence, après complétion du sous-workflow, l'exécution doit reprendre au node suivant la référence dans le parent.
|
|
**Validates: Requirements 2.1, 2.2**
|
|
|
|
**Property 6: Propagation d'erreur de sous-workflow**
|
|
*Pour toute* erreur survenant dans un sous-workflow, l'erreur doit être propagée au workflow parent avec le contexte complet (stack trace, état des variables, node en échec).
|
|
**Validates: Requirements 2.5**
|
|
|
|
**Property 7: Détection de similarité pour fusion**
|
|
*Pour toute* paire de workflows avec un score de similarité supérieur à 0.9, le système doit générer une suggestion de fusion.
|
|
**Validates: Requirements 3.1**
|
|
|
|
**Property 8: Préservation des chemins lors de la fusion**
|
|
*Pour tout* merge de workflows A et B, le workflow résultant doit contenir tous les chemins uniques de A et tous les chemins uniques de B.
|
|
**Validates: Requirements 3.2**
|
|
|
|
**Property 9: Combinaison des statistiques lors de la fusion**
|
|
*Pour tout* merge de workflows A et B, les statistiques du résultat doivent être la combinaison (somme ou union selon le type) des statistiques de A et B.
|
|
**Validates: Requirements 3.3**
|
|
|
|
**Property 10: Détection de séquences communes**
|
|
*Pour toute* séquence de nodes identique apparaissant dans N workflows (N >= 2), le système doit la détecter et suggérer son extraction.
|
|
**Validates: Requirements 4.1**
|
|
|
|
**Property 11: Remplacement par références après extraction**
|
|
*Pour toute* extraction de séquence en sous-workflow, toutes les occurrences originales doivent être remplacées par des nodes de référence pointant vers le nouveau sous-workflow.
|
|
**Validates: Requirements 4.2**
|
|
|
|
**Property 12: Priorisation des séquences fréquentes**
|
|
*Pour toute* séquence apparaissant dans 3 workflows ou plus, elle doit être marquée comme candidat haute priorité.
|
|
**Validates: Requirements 4.3**
|
|
|
|
**Property 13: Validation des dépendances**
|
|
*Pour toute* tentative de suppression d'un sous-workflow avec des dépendants, un avertissement doit être généré listant tous les workflows affectés. *Pour toute* configuration créant une dépendance circulaire, elle doit être rejetée avec une erreur.
|
|
**Validates: Requirements 5.2, 5.3**
|
|
|
|
**Property 14: Incrémentation du compteur de boucle**
|
|
*Pour toute* boucle, après chaque itération complète, le compteur doit être incrémenté exactement de 1.
|
|
**Validates: Requirements 6.2**
|
|
|
|
**Property 15: Terminaison de boucle à la limite**
|
|
*Pour toute* boucle avec une limite de N itérations, elle doit terminer après exactement N itérations (ou moins si la condition de sortie est atteinte).
|
|
**Validates: Requirements 6.3**
|
|
|
|
**Property 16: Garde de sécurité des boucles**
|
|
*Pour toute* boucle, le nombre d'itérations ne doit jamais dépasser 1000, et un warning doit être loggé si cette limite est atteinte.
|
|
**Validates: Requirements 6.5**
|
|
|
|
**Property 17: Ordre d'évaluation des branches conditionnelles**
|
|
*Pour tout* node conditionnel avec plusieurs branches dont les conditions sont vraies, seule la première branche (par ordre de priorité) doit être exécutée.
|
|
**Validates: Requirements 7.2**
|
|
|
|
**Property 18: Branche par défaut ou erreur**
|
|
*Pour tout* node conditionnel où aucune condition n'est satisfaite, la branche par défaut doit être exécutée si elle existe, sinon une erreur doit être levée.
|
|
**Validates: Requirements 7.3**
|
|
|
|
**Property 19: Passage du contexte de déclenchement**
|
|
*Pour tout* déclenchement de trigger, le contexte (timestamp, chemin fichier ou élément détecté) doit être passé au workflow.
|
|
**Validates: Requirements 8.4**
|
|
|
|
**Property 20: Gestion des déclenchements concurrents**
|
|
*Pour tout* workflow avec plusieurs triggers configurés, les exécutions concurrentes doivent être gérées selon la configuration (concurrent ou queue).
|
|
**Validates: Requirements 8.5**
|
|
|
|
**Property 21: Lecture de variable avec défaut**
|
|
*Pour toute* lecture d'une variable globale non définie, la valeur par défaut spécifiée doit être retournée.
|
|
**Validates: Requirements 9.2**
|
|
|
|
**Property 22: Préservation de l'état final des variables**
|
|
*Pour toute* chaîne de workflows complétée, le log d'exécution doit contenir l'état final de toutes les variables globales.
|
|
**Validates: Requirements 9.3**
|
|
|
|
## Error Handling
|
|
|
|
### Erreurs de chaînage
|
|
- **ChainValidationError** : Incompatibilité entre workflows
|
|
- **ChainExecutionError** : Échec pendant l'exécution d'une chaîne
|
|
- **VariableTransferError** : Échec du transfert de variables
|
|
|
|
### Erreurs de sous-workflows
|
|
- **SubWorkflowNotFoundError** : Référence vers un sous-workflow inexistant
|
|
- **CircularDependencyError** : Dépendance circulaire détectée
|
|
- **SubWorkflowExecutionError** : Échec dans un sous-workflow
|
|
|
|
### Erreurs de boucles
|
|
- **LoopSafetyLimitError** : Limite de sécurité atteinte (1000 itérations)
|
|
- **LoopConditionError** : Erreur lors de l'évaluation de la condition
|
|
|
|
### Erreurs de conditions
|
|
- **NoMatchingBranchError** : Aucune branche ne correspond et pas de défaut
|
|
- **ConditionEvaluationError** : Erreur lors de l'évaluation d'une condition
|
|
|
|
### Erreurs de triggers
|
|
- **TriggerConfigurationError** : Configuration de trigger invalide
|
|
- **ConcurrentExecutionError** : Erreur de gestion des exécutions concurrentes
|
|
|
|
## Testing Strategy
|
|
|
|
### Bibliothèque de Property-Based Testing
|
|
Le projet utilise **Hypothesis** pour Python, déjà configuré dans le projet.
|
|
|
|
### Tests unitaires
|
|
- Tests des composants individuels (ChainConfig, LoopConfig, etc.)
|
|
- Tests des validations (compatibilité, dépendances circulaires)
|
|
- Tests des cas limites (boucle vide, chaîne d'un seul workflow)
|
|
|
|
### Tests property-based
|
|
Chaque propriété de correction sera implémentée comme un test property-based avec :
|
|
- Minimum 100 itérations par test
|
|
- Annotation explicite référençant la propriété du design
|
|
- Format : `**Feature: workflow-composition, Property {number}: {property_text}**`
|
|
|
|
### Stratégie de génération
|
|
- Générateurs de workflows aléatoires avec nodes, transitions, et configurations
|
|
- Générateurs de configurations de chaînage valides et invalides
|
|
- Générateurs de conditions visuelles simulées
|
|
- Générateurs de contextes d'exécution avec variables
|
|
|
|
### Tests d'intégration
|
|
- Exécution de chaînes complètes avec plusieurs workflows
|
|
- Exécution de boucles avec conditions visuelles simulées
|
|
- Déclenchement de triggers et vérification du contexte
|