feat(session-limits): LS1 — couche domaine (détection + plan de reprise)

Pose les briques pures du domaine pour la gestion des limites de session
des agents (état en mémoire, aucun schéma de persistance modifié) :
- session_limit.rs (nouveau) : SessionLimit, ResumePlan, RateLimitSource,
  plan_resume (calcul du plan de reprise annulable).
- ports.rs : variante ReplyEvent::RateLimited.
- readiness.rs : variante ReadinessSignal::RateLimited + classify.
- profile.rs : RateLimitPattern + champ + builder.
- events.rs : 5 variantes DomainEvent pour le cycle de vie limite/reprise.
- lib.rs : module + re-exports.

Tests QA inline (#[cfg(test)]) : 24 tests dédiés.
`cargo test -p domain` = 165 passed / 0 failed, zéro régression.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-16 14:33:57 +02:00
parent fa5b826df5
commit 0bf1eb3b11
6 changed files with 647 additions and 1 deletions

View File

@ -225,6 +225,26 @@ pub enum ReplyEvent {
/// jusqu'au [`ReplyEvent::Final`]. Un tour comporte ≥0 `Heartbeat`, jamais
/// d'obligation d'en émettre.
Heartbeat,
/// **Limite de session/débit atteinte** (model-agnostique, ARCHITECTURE §21) :
/// l'adapter structuré a observé que le moteur a suspendu l'agent pour cause de
/// quota (Claude `rate_limit_event` → `rate_limit_info.resetsAt`, ou équivalent).
/// Porte un fait neutre : « limité, reset à T (peut-être) ». Aucun détail propre
/// à une CLI ne franchit la frontière (forme du `rate_limit_event`, format de
/// l'heure…) — tout cela reste confiné à l'adapter (cf. §21.2-T2).
///
/// **Jamais terminal** (cf. §21.2-T4) : exactement comme [`ReplyEvent::Heartbeat`],
/// un `RateLimited` **ne clôt pas** le flux — il s'intercale et le flux continue
/// jusqu'au [`ReplyEvent::Final`] **ou** jusqu'à une clôture du flux. Conséquence
/// pour les consommateurs : un tour clos **sans `Final`** parce que limité doit
/// être traité comme une **fin gracieuse limitée** (et non comme une erreur « flux
/// clos sans Final »), dès lors qu'un `RateLimited` a été vu dans le tour.
RateLimited {
/// Instant de réinitialisation de la limite, en **époche-millisecondes**
/// (homogène avec [`Clock::now_millis`]). `None` quand le moteur n'a pas
/// fourni d'heure de reset exploitable ⇒ pas de reprise auto possible (filet
/// humain, §21.1 niveau 3). Jamais une `Instant` monotone (cf. §21.2-T1).
resets_at_ms: Option<i64>,
},
/// **Événement terminal déterministe** d'un tour : l'adapter l'émet quand il a
/// lu le message `result` documenté de la CLI. Porte le contenu final agrégé.
/// Après `Final`, le flux se termine (plus aucun événement).