ADR-0042 — Convention nominale master_agent / simple_agent
Status : Accepted Date : 2026-05-01 Deciders : l.bizi (NEX-435) Tickets : NEX-435 (Story 2.1)
Contexte
Le code applicatif manipule deux types d'agents avec plusieurs casses et synonymes historiques, ce qui produit du code défensif comme :
ts
// services/orchestrator/.../process-master-agent-cash-in.use-case.ts:109
if (
masterAgentDetails.agentTypeCode !== 'MASTER_AGENT' &&
masterAgentDetails.agentTypeCode !== 'master_agent'
) {
throw new Error(...);
}Ce check inline est dupliqué à plusieurs endroits (cash-in, cash-out, futurs guards de la Story 2.1). Toute permission ou guard ajouté doit choisir une casse, sous peine de divergence silencieuse.
Variantes observées en production :
master_agent/simple_agent(rôlesauth.roles, code mobile)MASTER_AGENT/SIMPLE_AGENT(champagentTypeCodecôtécustomer-profiles-kycselon les données)master/distribution(anciennes valeurs DB pour les types d'agents — non confirmé au moment de cet ADR)
Décision
Convention canonique : master_agent et simple_agent en lowercase, snake_case.
- Nouveau code (permissions, guards, helpers, logs) écrit après cet ADR utilise exclusivement
master_agent/simple_agent. - Comparaisons en lecture sur
agentTypeCodepassent par un helperisMasterAgent(code)/isSimpleAgent(code)qui normalise en lowercase avant comparaison — tolère les variantes existantes en production sans casser le système. - Refacto progressive : les comparaisons inline des use-cases existants sont remplacées par les helpers au fil des touches sur ces fichiers. Pas de big-bang.
- Migration de données des éventuelles variantes
MASTER_AGENTcôtécustomer-profiles-kycn'est pas dans le scope de cet ADR — elle se fera dans un ticket dédié post-stabilisation, après audit du contenu réel.
Helper TypeScript
À créer dans services/orchestrator/src/core/common/utils/agent-type.utils.ts (et copié/lié vers les autres services qui en ont besoin) :
ts
export const MASTER_AGENT_TYPE_CODE = 'master_agent' as const;
export const SIMPLE_AGENT_TYPE_CODE = 'simple_agent' as const;
export function isMasterAgent(agentTypeCode?: string | null): boolean {
return agentTypeCode?.toLowerCase() === MASTER_AGENT_TYPE_CODE;
}
export function isSimpleAgent(agentTypeCode?: string | null): boolean {
return agentTypeCode?.toLowerCase() === SIMPLE_AGENT_TYPE_CODE;
}Conséquences
Positives
- Une seule source de vérité pour la comparaison de type d'agent — élimination du code défensif dupliqué.
- Tests unitaires concentrés sur le helper plutôt qu'éparpillés sur chaque use-case.
- Lisibilité :
isMasterAgent(agent.agentTypeCode)est plus explicite qu'une comparaison inline avec deux casses. - Tolérance au legacy en lecture sans complexité côté appelant.
Négatives / risques
- Le helper masque la divergence sous-jacente — un audit reste nécessaire pour migrer les données vers la casse canonique à terme.
- Risque de drift si un nouveau dev compare inline plutôt que d'utiliser le helper. Mitigation : règle ESLint custom envisageable si la dérive se manifeste (hors scope V1).
Alternatives écartées
- Big-bang migration de toutes les valeurs DB : trop risqué (impacte plusieurs services, foreign keys), reporté à un ticket dédié après stabilisation de la feature master agent.
- Conserver les comparaisons inline : statu quo, échoue dès qu'un nouveau check est ajouté avec une casse différente.
- Convention UPPERCASE (
MASTER_AGENT) : minoritaire dans le code applicatif, retiendrait les patterns SQL existants (rôlesmaster_agent,simple_agent) en désaccord avec le reste — coût plus élevé.
Suivi
- L'introduction du helper et son usage dans les
MasterAgentGuard/master-rbac.integration.spec.ts(Story 2.1) valident l'ADR par la pratique. - Les use-cases
process-master-agent-cash-in.use-case.tsetprocess-master-agent-cash-out.use-case.tsseront refactorés vers le helper lors des Stories 2.5 et 2.6 respectivement. - Aucune modification de migration SQL n'est nécessaire pour cet ADR ; le rôle
master_agentexiste déjà côtéauth.roles(migration 001).