/** * cli:audit-ba — rules/actors.ts (ACT-001, ACT-002, ACT-006 — project scope). * Mirrors business-analyse/audit-actors/SKILL.md verbatim. */ import { finding, fold, type RuleDef } from './registry.js' const DIM = 'actors' as const export const ACTOR_RULES: RuleDef[] = [ { id: 'ACT-001', dimension: DIM, scope: 'project', kind: 'mechanical', evaluate({ model }) { const total = model.totals.actors return { findings: [ total === 0 ? finding('ACT-001', DIM, 'err', {}, 'Aucun acteur déclaré dans le projet — lancer /ba-create-actors.') : finding('ACT-001', DIM, 'ok', {}, `${total} acteur(s) déclaré(s) sur ${model.apps.length} application(s).`), ], } }, }, { id: 'ACT-002', dimension: DIM, scope: 'project', kind: 'mechanical', evaluate({ model }) { // Same code across apps = the documented multi-app reuse (ok). A // duplicate is: same folded label under DIFFERENT codes, or the same // code with divergent Type/Catégorie definitions across apps. const byLabel = new Map>() const byCode = new Map; cats: Set }>() for (const app of model.apps) { for (const a of app.actors) { const label = fold(a.label) if (!byLabel.has(label)) byLabel.set(label, new Set()) byLabel.get(label)!.add(a.code.toUpperCase()) const code = a.code.toUpperCase() if (!byCode.has(code)) byCode.set(code, { types: new Set(), cats: new Set() }) if (a.type) byCode.get(code)!.types.add(fold(a.type)) if (a.categorie) byCode.get(code)!.cats.add(fold(a.categorie)) } } const evidence: string[] = [] for (const [label, codes] of byLabel) { if (codes.size > 1) evidence.push(`label « ${label} » porté par ${[...codes].sort().join(' + ')}`) } for (const [code, def] of byCode) { if (def.types.size > 1) evidence.push(`${code} : Type divergent entre applications (${[...def.types].join(' vs ')})`) if (def.cats.size > 1) evidence.push(`${code} : Catégorie divergente entre applications (${[...def.cats].join(' vs ')})`) } return { findings: [ evidence.length > 0 ? finding( 'ACT-002', DIM, 'err', {}, `${evidence.length} doublon(s)/divergence(s) d'acteur — fusionner sous un code unique ou réconcilier les définitions via /ba-create-actors.`, evidence, ) : finding('ACT-002', DIM, 'ok', {}, 'Aucun doublon de label (insensible à la casse) ni définition divergente.'), ], } }, }, { id: 'ACT-006', dimension: DIM, scope: 'project', kind: 'mechanical', evaluate({ model }) { const valid = new Set(['internal', 'external', 'system']) const evidence: string[] = [] for (const app of model.apps) { for (const a of app.actors) { const t = fold(a.type ?? '') if (!valid.has(t)) evidence.push(`${a.code} (${a.label})${a.type ? ` : Type « ${a.type} »` : ' : Type absent'}`) } } return { findings: [ evidence.length > 0 ? finding( 'ACT-006', DIM, 'warn', {}, `${evidence.length} acteur(s) sans Type valide (internal | external | system) — compléter via /ba-create-actors.`, evidence, ) : finding('ACT-006', DIM, 'ok', {}, 'Tous les acteurs sont typés (internal | external | system).'), ], } }, }, ]