test(persistence): P8a — déterminisme registre + cohérence de clé end-to-end
Scelle la cohérence corrigée en P8a : - infra (5) : resolve stable à travers 2 instances de registre (restart), resolve.id == for_pair == uuid agent, commutativité, distinction des paires - application (2) : round-trip réel « handoff sauvé sous la clé resolve ↔ rechargé au relancement de la cellule portant cette clé » + contre-épreuve (handoff d'une autre paire ⇒ pas de reprise) Clarifie : une cellule neuve (conversation_id=None) ne charge pas de handoff ; la reprise opère à la réouverture. domain/infra/application verts, zéro régression. Code de prod non modifié. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -194,4 +194,94 @@ mod tests {
|
||||
let reg = InMemoryConversationRegistry::new();
|
||||
assert!(reg.get(ConversationId::new_random()).is_none());
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Bloc 1 — déterminisme du registre (§19 : la clé de persistance = id de paire,
|
||||
// déterministe et **stable au redémarrage**). Scelle l'invariant que Main a
|
||||
// rattrapé : deux instances neuves du registre (= deux démarrages de l'IDE)
|
||||
// doivent dériver **le même** id pour la même paire, et cet id doit être
|
||||
// **exactement** `ConversationId::for_pair`, sinon la clé sous laquelle le
|
||||
// handoff/log est rangé dériverait au redémarrage.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn resolve_is_stable_across_registry_restart() {
|
||||
// Deux instances neuves (by_pair/by_id vides) simulent deux démarrages de
|
||||
// l'IDE : la même paire User↔Agent doit produire le **même** id.
|
||||
let a = agent(7);
|
||||
let first = InMemoryConversationRegistry::new()
|
||||
.resolve(ConversationParty::User, a)
|
||||
.id;
|
||||
let second = InMemoryConversationRegistry::new()
|
||||
.resolve(ConversationParty::User, a)
|
||||
.id;
|
||||
assert_eq!(
|
||||
first, second,
|
||||
"id de paire stable au redémarrage (registre neuf ⇒ même id)"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_is_stable_across_registry_restart_agent_agent() {
|
||||
// Même garantie pour une paire Agent↔Agent (dérivation XOR commutative).
|
||||
let x = agent(11);
|
||||
let y = agent(13);
|
||||
let first = InMemoryConversationRegistry::new().resolve(x, y).id;
|
||||
let second = InMemoryConversationRegistry::new().resolve(y, x).id;
|
||||
assert_eq!(
|
||||
first, second,
|
||||
"id de paire Agent↔Agent stable au redémarrage, insensible à l'ordre"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_id_equals_for_pair_user_agent() {
|
||||
// Alignement de clé : l'id que le registre matérialise == le repli pur
|
||||
// `for_pair` == `from_uuid(agent)` (la clé que P8a/`resolve_conversation`
|
||||
// dérivent pour la paire canonique User↔Agent).
|
||||
let agent_id = AgentId::from_uuid(uuid::Uuid::from_u128(42));
|
||||
let party = ConversationParty::agent(agent_id);
|
||||
let resolved = InMemoryConversationRegistry::new()
|
||||
.resolve(ConversationParty::User, party)
|
||||
.id;
|
||||
assert_eq!(
|
||||
resolved,
|
||||
ConversationId::for_pair(ConversationParty::User, party),
|
||||
"resolve == for_pair (alignement de clé)"
|
||||
);
|
||||
assert_eq!(
|
||||
resolved,
|
||||
ConversationId::from_uuid(agent_id.as_uuid()),
|
||||
"User↔Agent ⇒ id == uuid de l'agent (repli resolve_conversation)"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_id_equals_for_pair_agent_agent_commutative() {
|
||||
// Commutativité et alignement sur `for_pair` pour Agent↔Agent.
|
||||
let x = agent(101);
|
||||
let y = agent(202);
|
||||
let reg = InMemoryConversationRegistry::new();
|
||||
let id_xy = reg.resolve(x, y).id;
|
||||
let id_yx = reg.resolve(y, x).id;
|
||||
assert_eq!(id_xy, id_yx, "resolve(a,b) == resolve(b,a)");
|
||||
assert_eq!(
|
||||
id_xy,
|
||||
ConversationId::for_pair(x, y),
|
||||
"resolve == for_pair (Agent↔Agent)"
|
||||
);
|
||||
assert_eq!(reg.len(), 1, "une seule conversation pour la paire {{a,b}}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn distinct_pairs_yield_distinct_ids_across_kinds() {
|
||||
// Deux paires distinctes ⇒ deux ids distincts (pas de collision de clé).
|
||||
let reg = InMemoryConversationRegistry::new();
|
||||
let user_a = reg.resolve(ConversationParty::User, agent(1)).id;
|
||||
let user_b = reg.resolve(ConversationParty::User, agent(2)).id;
|
||||
let a_b = reg.resolve(agent(1), agent(2)).id;
|
||||
assert_ne!(user_a, user_b, "User↔A ≠ User↔B");
|
||||
assert_ne!(user_a, a_b, "User↔A ≠ A↔B");
|
||||
assert_ne!(user_b, a_b, "User↔B ≠ A↔B");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user