Crée les 3 dataclasses du modèle Mandat/Protocoles/Scènes v0.3 dans core/cognition/, standalone (aucun branchement runtime), avec sérialisation JSON explicite et tests offline. Préparation des phases : - Phase 2.1 plan : objet Trace (mandate_id, intention_id, scene_id, affordance_signature, expected_retour, level_of_delegation) - Workpack A : SceneExpected (monitor_index, app_name, title_patterns, title_anti, window_rect_hint, scene_role, accepted_transitions, stability_ms) + helper matches_title() - Workpack B : Precondition (kind, window_title_must_contain/anti, critic_question, verify_timeout_ms) + PreconditionRecovery (max_attempts, on_recovery_fail, actions) Toutes les dataclasses sont frozen, immutables, avec to_dict/from_dict tolérants (champs vides/None -> instance vide). Validation au __post_init__ pour Precondition.kind et PreconditionRecovery.on_recovery_fail. Aucune dépendance runtime obligatoire : si l'objet n'est pas posé sur une action, fallback comportement actuel. Aucune modif executor / api_stream / replay_engine / grounding. Tests : 22/22 passent (sérialisation JSON, contrats from_dict tolérants, validation kinds, helpers matches_title/check_title, anti-intention). Tag rollback : rollback/pre-cognition-dataclasses-2026-05-25_0610
60 lines
2.0 KiB
Python
60 lines
2.0 KiB
Python
"""Trace causale d'une action — modèle Mandat/Protocoles/Scènes v0.3.
|
|
|
|
Cf. docs/architecture/MODELE_MANDAT_PROTOCOLS_LEA_2026-05-25_v0.3_ARBITRAGES_DOM.md
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field, asdict
|
|
from typing import Any, Dict, Optional
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Trace:
|
|
"""Contrat unificateur transporté du build au runtime à la preuve.
|
|
|
|
Tous les champs sont optionnels (str vide / None) pour permettre une
|
|
introduction progressive sans casser les actions existantes qui n'en
|
|
portent pas. Fallback : comportement actuel si trace absente.
|
|
|
|
Attributs
|
|
mandate_id : ID du mandat humain de niveau supérieur
|
|
intention_id : ID du sous-but courant servant le mandat
|
|
scene_id : ID de la scène d'intention pertinente
|
|
affordance_signature: signature stable de l'affordance ciblée
|
|
expected_retour : description courte du retour attendu
|
|
level_of_delegation : N0..N4 (cf v0.3 arbitrage 3)
|
|
"""
|
|
|
|
mandate_id: str = ""
|
|
intention_id: str = ""
|
|
scene_id: str = ""
|
|
affordance_signature: str = ""
|
|
expected_retour: str = ""
|
|
level_of_delegation: int = 0
|
|
|
|
def to_dict(self) -> Dict[str, Any]:
|
|
return asdict(self)
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: Optional[Dict[str, Any]]) -> "Trace":
|
|
if not data:
|
|
return cls()
|
|
return cls(
|
|
mandate_id=str(data.get("mandate_id", "") or ""),
|
|
intention_id=str(data.get("intention_id", "") or ""),
|
|
scene_id=str(data.get("scene_id", "") or ""),
|
|
affordance_signature=str(data.get("affordance_signature", "") or ""),
|
|
expected_retour=str(data.get("expected_retour", "") or ""),
|
|
level_of_delegation=int(data.get("level_of_delegation", 0) or 0),
|
|
)
|
|
|
|
def is_empty(self) -> bool:
|
|
return not (
|
|
self.mandate_id
|
|
or self.intention_id
|
|
or self.scene_id
|
|
or self.affordance_signature
|
|
or self.expected_retour
|
|
)
|