Trois corrections issues du code review multi-agent sur la PR audit-log :
- AuditListener : reset defensif de pendingLogs en debut de onFlush. Si
un flush precedent a leve une exception avant postFlush (qui n'est
jamais appele sur un flush rate), le state listener gardait des
changements jamais committes, ecrits a tort par le prochain postFlush
reussi — audit_log pouvait donc contenir des lignes decrivant des
evenements qui n'ont pas eu lieu en DB. Test de regression via
Reflection pour injecter un log orphelin et verifier qu'il n'arrive
pas dans audit_log.
- AuditLogProvider : validation explicite des filtres performed_at[after]
et performed_at[before] (strtotime) + whitelist stricte sur `action`
(create|update|delete). Avant, un input malforme remontait jusqu'a
Postgres et faisait un 500 (SQLSTATE[22007]). Desormais 400 explicite,
pas de log pollue.
- doc/audit-log.md : ajoute une section "Contrat" qui explicite ce que
audit_log garantit (journal des intentions appliquees par l'ORM) et ne
garantit PAS (reflet exact du commit outermost — une ligne audit peut
persister si une transaction outermost rollback apres un flush inner
reussi, parce que l'audit ecrit sur une connexion DBAL dediee).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Resout les 5 findings de la review automatique + couverture ManyToMany
annoncee dans CLAUDE.md :
- AuditListener : resolution de la classe via ClassMetadata plutot que
`$entity::class` direct (defense proxy Doctrine : sous ORM 2 les lazies
sont des `Proxies\__CG__\...`). Test de regression via getReference().
- AuditListener : capture des modifications de collections to-many
(OneToMany / ManyToMany) via getScheduledCollectionUpdates /
getScheduledCollectionDeletions. Les diffs sont mergees dans le
changeset existant ou creent une entree "update" dediee.
- AuditLogResource + Provider : filtre multi-valeurs
`entity_type[]=X&entity_type[]=Y` (IN clause DBAL via
ArrayParameterType::STRING), endpoint `/audit-log-entity-types` pour
alimenter le MalioSelectCheckbox cote front.
- audit-log.vue : refonte complete. Passage a `MalioDataTable`,
composants `Malio*` (MalioInputText, MalioSelectCheckbox, MalioButton),
suppression complete de la persistance URL (`readQuery` / `syncQuery`
/ `route.query`). `datetime-local` conserve avec TODO pointant
l'exception CLAUDE.md.
- AuditTimeline : fix du saut d'items 11-30. `PAGE_SIZE = 10` aligne
avec un `itemsPerPage=10` passe au backend. Token anti-race pour
ignorer les reponses tardives quand l'entite affichee change.
- AuditLogDetail : affichage des diffs de collections to-many (+ / -)
dans le tableau field/old/new existant.
- logout.vue : ajout du `resetAuditLog()` au logout pour eviter qu'un
user suivant (meme onglet) voie l'etat audit de l'ancien.
- Permission / Role / Site : marquage `#[Auditable]`.
- Version bump 0.1.32 → 0.1.34.
Tests : 228 / 228 (221 assertions → 851, dont regressions proxy + M2M).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Blocker
- Frontend attendait `hydra:member` / `hydra:totalItems` / `hydra:view` mais
API Platform 4 sert `member` / `totalItems` / `view` (sans prefixe) sous
ld+json, et un tableau plat sous json. Consequence : tableau admin et
timeline silencieusement vides.
Fix : `useAuditLog` force `Accept: application/ld+json` (necessaire pour
obtenir l'objet Hydra avec pagination), types `HydraCollection`/`HydraView`
renommes, composants accedent aux proprietes sans prefixe. Nouveau test
fonctionnel verrouille le format.
Should-fix
- `AuditLogWriter` : ajout de `'id' => Types::GUID` pour expliciter le type
natif PG `uuid` (fonctionnait par cast implicite mais l'intention etait
floue).
- `AuditListener` docblock : documente que le DQL bulk DELETE/UPDATE et
`Connection::executeStatement()` bypassent le listener (onFlush non
appele). Piege pour les futures commandes de purge.
- `AuditLogResource` : ajout d'une regex UUID dans `requirements` de
l'operation Get — un `GET /api/audit-logs/not-a-uuid` produisait un 500
(cast PG rejete) au lieu d'un 404.
- `audit-log.vue` : le watcher des filtres faisait `filters.page = 1` ce
qui declenchait le watcher de `page`, causant deux `loadEntries()` en
parallele. Fusionne : la navigation page appelle `loadEntries()`
directement depuis `goPrevious`/`goNext`, plus de watcher dedie.
- `useAuditLog.fetchEntityLogs` : bypass du cache `lastCollection` pour ne
pas polluer la reference page-level quand la timeline est ouverte.
- `AuditTimeline.vue` : remplacement du `<div v-if="!canView"/>` vide par
un `v-if` sur le wrapper — aucun DOM quand l'utilisateur n'a pas le droit.
- `AuditListenerTest` tag : retire le `_` (wildcard LIKE SQL) du prefix
pour eviter un faux negatif de match cross-test.
- `AuditLogApiTest` : proprietes `auditConnection` / `runTag` nullable et
tearDown guarde, sinon un echec setUp provoquait un fatal typed-property
au lieu de propager l'exception d'origine.
Stabilite suite de tests
- `doctrine.yaml when@test` : `idle_connection_ttl: 1` sur les deux
connexions pour eviter l'accumulation de connexions orphelines.
- tearDown des tests audit : `close()` explicite sur la connexion audit
apres chaque test.
- `docker-compose.yml` : `max_connections=300` sur la DB dev (defaut PG=100
insuffisant pour 220+ tests * 2 connexions/test).
Implemente le journal d'audit append-only sur toutes les mutations Doctrine
des entites portant #[Auditable]. Couvre les 5 tickets de doc/audit-log.md :
1. Table PG audit_log (uuid PK, jsonb changes, index entity/time/performer)
+ AuditLogWriter (DBAL connexion dediee audit, blacklist defense-in-depth
sur password/plainPassword/token/secret) + RequestIdProvider (UUID v4 par
requete HTTP principale).
2. Attributs Auditable / AuditIgnore dans Shared/Domain/Attribute/
+ AuditListener (onFlush capture + postFlush ecriture hors transaction ORM,
pattern swap-and-clear, erreurs loguees jamais propagees). User annote.
3. API Platform read-only /api/audit-logs (permission core.audit_log.view)
avec filtres entity_type / entity_id / action / performed_by / plage
performed_at + DbalPaginator implementant PaginatorInterface (hydra:view
genere automatiquement).
4. Page admin /admin/audit-log : tableau pagine, filtres persistes en query
params, row expandable (diff + timeline de l'entite), entree sidebar avec
permission. Composable useAuditLog avec resetAuditLog() auto-enregistre
sur onAuthSessionCleared.
5. Composant AuditTimeline reutilisable : garde permission, lazy loading,
dates relatives FR, skeleton loader.
Fix connexe : phpunit.dist.xml forcait APP_ENV=dev via <env> ce qui cablait
framework.test=false et rendait test.service_container indisponible ; le
JWT_PASSPHRASE ne matchait pas non plus les cles dev. Corrige en meme temps
pour debloquer la suite de tests.