# Rapport v1.20.0 — Le connecteur en mode administrateur, et des écrans qui ne bloquent plus

Partie MOBDIE (§C, §D, §E) du lot commun `PROMPT-LOT-socle-v4.35.0-mobdie-v1.20.0.md` (07/11/2026).
Base `v1.19.2` (`197d473`, en prod). Le socle v4.35.0 est livré à part (même conception du
connecteur : niveau Administrateur, plan, idempotence, 429, go de session, 7 jours, `lecture_libre`).

## Connecteur (§C.1, §C.2)

- **Niveau Administrateur** (`ClaudeToken::NIVEAU_ADMINISTRATEUR`, « Administrateur — droits cochés un
  par un ») : peut tout ce que peut « actions » ; un droit n'est effectif que sur un jeton
  Administrateur ET case cochée (`aLeDroit()`). `Contexte::exigerDroit()` refuse en nommant la case :
  « Refusé : ce geste exige le droit administrateur « … ». Le gérant le coche dans Paramétrage → Accès
  Claude, sur un jeton de niveau Administrateur. » Catalogue : `App\Mcp\DroitsAdministrateur`.

  | Clé | Libellé de la case | Outils |
  |---|---|---|
  | `operations_modifier` | Modifier librement une opération (compte, sens, catégorie, date, montant) | `operation_modifier` (sens ; compte/date/montant d'une opération rattachée) |
  | `corbeille` | Mettre à la corbeille et restaurer (jamais de suppression définitive) | `corbeille` |
  | `operations_couper` | Couper une opération en plusieurs (somme conservée) | `operation_couper` |
  | `encaissements_rattacher` | Rattacher un encaissement à une échéance, ou remplacer un « payé hors trésorerie » | `encaissement_rattacher_echeance`, `facture_remplacer_hors_tresorerie` |
  | `dossiers_modifier` | Modifier contrats, échéances et clients au-delà du périmètre « actions » | `echeance_modifier`, `contrat_modifier`, `client_modifier` (identité légale) |

  Verrous absolus, même avec la case : montant / sens d'un encaissement client, tout ce qui touche
  l'encaissement d'une facture émise, prime versée, apport lié, numéro émis (une facture numérotée ne
  va jamais à la corbeille ; une échéance payée ou avec acompte ne se modifie pas), envoi à un tiers,
  suppression définitive, mots de passe / jetons / utilisateurs / rôles / comptes bancaires.
- **Accès Claude** : une case par droit sur chaque jeton Administrateur (éteintes par défaut), date de
  dernière utilisation, révocation d'un clic, « Go de session (30 min) » / « Couper le go » (couper
  ramène aussi les codes déjà émis à 10 min). Chaque changement → une ligne `claude_journal`
  méthode `acces.droits`, paramètres `quoi`, `avant`, `apres`, `utilisateur`, statut `ok`.
- **`plan_executer`** : 1 à 200 `{outil, arguments}` ; simulation dans la transaction annulée du
  serveur (chaque action validée par le schéma de son outil, planifiée puis appliquée dans l'ordre),
  un plan numéroté, un détail, un code ; confirmation = même boucle dans une transaction, empreinte du
  plan comparée, tout ou rien ; étapes d'annulation concaténées puis recalées sur l'état final
  (`Annulation::rafraichir`) ; une entrée de journal. Interdits : `plan_executer`, `action_annuler`,
  la lecture, et `demande_avancer` / `relance_preparer` (effet hors base : e-mail, PDF — voir écarts).
  `set_time_limit(300)` si la limite du serveur est plus basse. `Schema` admet `additionalProperties:
  true` (objet libre), une propriété sans type et `minItems`.
- **Idempotence** : paramètre `idempotence` (≤ 80) sur toute action ; table `claude_idempotence`
  (unique `token_id`+`cle`), clé retirée avant l'empreinte ; réservée DANS la transaction
  d'application (deux applications simultanées sous la même clé ne passent pas), résultat expurgé
  gardé ensuite ; rejouée → résultat + `deja_applique: true`, même sans code ou code périmé ; autre
  outil / autres paramètres → refus explicite.
- **Débit** : 120 / minute / jeton ; au-delà 429 + `Retry-After` + `X-Mcp-Refus: frequence`.
- **Go de session** : un code émis pendant le go expire à `go_session_jusqua` (jamais moins de 10 min).
- **Annulation sur 7 jours** (`ClaudeJournal::ANNULABLE_JOURS`) ; étape `remettre` (sortir de la
  corbeille) ; colonne `claude_journal.detail` (détail du plan appliqué), rendue par `journal_claude`
  (paramètre `id`) et par « Détail » sur l'écran Journal Claude.
- **`lecture_libre`** : objets `operations, factures, lignes_de_facture, clients, contrats, echeances,
  paiements, categories, demandes, primes, cabinets` ; filtres `= != < <= > >= contient entre nul
  non_nul` ; `champs`, `tri`, `ordre`, `limite` ≤ 200, `page`, `corbeille` (sans/avec/seulement) ;
  policy `viewAny` de l'objet ; champs = colonnes réelles moins le secret
  (`password|token|secret|hash|two_factor|recovery|api_key|remember`, `iban/rib/swift/bic` en mot
  entier) et moins la liste noire `Expurgation`.

## Métier (§C.3 à §C.7) — un seul service pour l'écran et le connecteur

- **C.3** `client_fiche` : `factures` (numéro, date, statut corbeille comprise, TTC, net, payée le,
  `payee_par` : paiements et opérations / hors trésorerie (motif) / historique repris) et `paiements`.
- **C.4** Liste des factures : filtre « Corbeille » (lève la portée sur la requête de base), badge
  « À la corbeille — rang NN/AAAA tenu », une pièce à la corbeille ne s'ouvre pas ; **contrôle 14**
  « Rang tenu par une pièce à la corbeille » (à vérifier) ; `EtatDeLaPiece` = statut et client à une
  source, lue par `mobdie:deployer-v1190` (ses deux tableaux), `mobdie:deployer-v1200`, le contrôle 14
  et `client_fiche`.
- **C.5** `FacturationDesEcheancesPayees::appariements()` : même client, même montant, J−10 à J+90,
  la plus proche d'abord, opérations sans paiement ; jamais appliqué sans `--operation` / `operation`.
- **C.6** `RattachementEncaissements::echeancesProposees()` : échéances échues non soldées du même
  client (fiche du tiers ou raison sociale = libellé du tiers) dont le reste dû = le montant ;
  contrôle 9 → `echeance_proposee` + « échéance proposée : … » ; bouton « Rattacher à cette
  échéance » (la fenêtre simule, le bouton confirme) ; outil `encaissement_rattacher_echeance`.
- **C.7** `remplacerHorsTresorerie()` : facture payée sans paiement, hors trésorerie OU historique
  repris ; opération d'entrée libre = net à encaisser ; paiement sans échéance pointant l'opération,
  `payment_id`, `payee_at` = date de l'opération, marque hors trésorerie retirée, statut « Payée »
  inchangé ; garde-fou « aucune opération créée ». Action « Remplacer par l'encaissement réel » de la
  liste des factures (sélection proposée, simulation affichée) ; outil
  `facture_remplacer_hors_tresorerie`. Annulable : la facture retrouve son hors trésorerie et son motif.

## Outils : 32 → 40 (18 → 19 lecture, 14 → 21 action)

Nouveaux : `lecture_libre` (lecture) ; `plan_executer`, `encaissement_rattacher_echeance`,
`facture_remplacer_hors_tresorerie`, `operation_couper`, `corbeille`, `echeance_modifier`,
`contrat_modifier` (action). Modifiés : `operation_modifier` (sens, administrateur, le paiement
suit), `client_modifier` (identité légale en administrateur), `client_fiche`, `journal_claude`,
`action_annuler` (7 jours), `facture_emettre_depuis_echeance` (fenêtre 90 jours).

## Migrations (3, additives et idempotentes)

- `2026_11_08_100000_le_niveau_administrateur_des_jetons_claude` — `claude_tokens.droits` (JSON),
  `go_session_jusqua` (datetime) ;
- `2026_11_08_100100_create_claude_idempotence_table` ;
- `2026_11_08_100200_le_detail_du_journal_claude` — `claude_journal.detail` (JSON).

## Fichiers

Nouveaux : `app/Console/Commands/DeployerV1200.php`, `app/Domaines/Facturation/Services/{EtatDeLaPiece,RattachementEncaissements}.php`,
`app/Mcp/{DroitsAdministrateur,Idempotence}.php`, `app/Mcp/Outils/Action/{PlanExecuter,EncaissementRattacherEcheance,FactureRemplacerHorsTresorerie,OperationCouper,Corbeille,EcheanceModifier,ContratModifier}Outil.php`,
`app/Mcp/Outils/Lecture/LectureLibreOutil.php`, `app/Models/Claude/ClaudeIdempotence.php`,
`resources/views/filament/partials/detail-journal-claude.blade.php`, les 3 migrations.
Modifiés : voir `git show --stat v1.20.0`. **Aucun fichier supprimé ni déplacé.**

## Tests

`tests/Feature/Mcp/ConnecteurV1200Test.php` (13) et `tests/Feature/V1200/LotV1200Test.php` (8) :
chaque droit refusé sans la case (libellé cité) et réussi avec ; un droit coché sur un jeton
« actions » ne vaut rien ; plan de 200 actions (simulation sans écriture, un code, action n°101
impossible → rien, confirmation → 199 opérations + une touchée deux fois, une entrée de journal au
détail de 200 actions, annulé d'un bloc) ; situation changée → confirmation refusée ; outils
interdits dans un plan ; clé anti-doublon rejouée (sans code, avec code périmé) sans rien
réappliquer, autre paramètre / autre outil refusés ; 429 + `Retry-After` + `X-Mcp-Refus` au 121ᵉ
appel ; annulation J+6 acceptée, J+8 refusée ; journal avant / après ; go de session (code valable
à +20 min ; coupé, expiré à +11 min) ; `lecture_libre` sans champ secret ; corbeille visible (liste,
contrôle 14, deployer) ; TIRIMI 21/05 ↔ 03/07 (J+96 exclu, la plus proche d'abord) ; PARAMAP
700 DH proposé et rattaché à l'écran ; 34/2026 ↔ 3 500 DH remplacé à l'écran ; hors trésorerie
remplacé par le connecteur puis annulé ; `client_fiche` liste factures et paiements ;
`mobdie:deployer-v1200` ; Accès Claude (cases, journalisation, go, refus au commercial).
Ajustés : compte des outils (40), compte des contrôles (14), arguments de démo des 8 outils.

Suite complète : **1 705 tests, 1 704 au vert** (509 + 1 181 + 15, en trois passes) ; 1 échec
préexistant propre au poste (`PortailSousDomaineTest` — `APP_URL` du `.env` du Mac). Un test
existant ajusté pour le contrôle 14 (`ControleFacturesOperationsTest` : « chaque contrôle une
fois »). Pint vert sur les fichiers du lot ; `npm run build` : `theme` et `app.css` changent
(classes des nouveaux écrans) → `public/build` complet dans le paquet.

⚠️ **Piège préexistant de la suite (hors lot)** : `App\Jobs\LirePieceDuPortail` pose
`set_time_limit(240)` sur le PROCESSUS ; en série, un test de `LectureRenforceeTest` arme donc une
limite de 240 s qui tue la suite plus loin (« Maximum execution time of 240 seconds exceeded »).
Contournement employé : trois passes (`tests/Unit tests/Domaines`, `tests/Feature` sans ce fichier,
puis ce fichier seul). Non corrigé ici (code de v1.17.0, hors périmètre) — à traiter dans un lot
ultérieur (rétablir la limite d'avant à la fin du job).

## Écarts au brief

- `demande_avancer` et `relance_preparer` sont exclus des plans : leur application envoie un e-mail
  au client ou dépose un PDF — la simulation d'un plan (qui APPLIQUE dans une transaction annulée)
  l'aurait réellement fait. Ils s'appellent seuls.
- Un plan qui contient une action non annulable (`facture_emettre_depuis_echeance`) n'est pas
  annulable par `action_annuler` (le plan le dit en simulation).
- Le go de session ne raccourcit jamais un code : expiration = max(fin du go, +10 min).
- Regex des secrets : `rib` / `iban` comparés en mot entier (sinon `siege_attribue` serait exclu).
- Un plan dont une action vise un objet CRÉÉ par une action précédente du même plan peut voir son
  empreinte changer (identifiants auto-incrémentés non rendus par l'annulation MySQL) : la
  confirmation est alors refusée, rien n'est écrit — relancer la simulation.
