From 7686904c43d255a2be9e65ac7bc1737ad3acb4c3 Mon Sep 17 00:00:00 2001 From: Matthieu Date: Fri, 19 Jun 2026 21:19:27 +0200 Subject: [PATCH] docs : log LST-61 audit log session learnings --- .claude/skills/ticket-executor/LEARNINGS.md | 22 +++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/.claude/skills/ticket-executor/LEARNINGS.md b/.claude/skills/ticket-executor/LEARNINGS.md index c833a48..7e9fca6 100644 --- a/.claude/skills/ticket-executor/LEARNINGS.md +++ b/.claude/skills/ticket-executor/LEARNINGS.md @@ -145,3 +145,25 @@ - **MCP status**: Toujours mettre "En cours" AVANT de commencer, "Terminé" APRÈS validation - **PostgreSQL gotchas**: Tester les queries SQL avec agrégation + locking sur PostgreSQL, pas MySQL - **Agents**: Les agents simples (1-3 fichiers) terminent en ~30s, les complexes (22 fichiers) en ~8min + +## Session 2026-06-19 (LST-61 / 1.3 — Audit log : #[Auditable], audit_log, AuditListener, resource) + +### Contexte +- Plan TDD dédié (`docs/superpowers/plans/2026-06-19-lst-61-audit-log.md`, Tasks A→F). Exécution : 1 sous-agent par task (A, B, C, D, E) en séquence, vérif + smoke par la session principale entre chaque ; Task F (validation finale + correctif front + learnings + push + statut) en direct. +- Infra portée VERBATIM depuis Starseed (réf canonique `/home/matthieu/dev_malio/Starseed`) : `AuditListener` byte-identique (`diff -q` OK), + 6 fichiers API (DTO/paginator/providers/resources) copiés tels quels — namespaces `App\Module\Core\...` et `App\Shared\Domain\Attribute\...` DÉJÀ alignés entre les deux projets, zéro adaptation. +- 6 commits impl (`934cf08` A, `d8553f0` B, `8c3699a` C, `90b8ca1` D, `e7af415` E, `9b26b43` fix front) + plan `fda03bd`. Tests : 147→157 verts. Branche `feat/lst-61-audit-log` empilée sur `feat/lst-57-rbac-fin`. + +### Patterns +- **Audit en 4 couches additives** : (1) marquage déclaratif `#[Auditable]`(TARGET_CLASS) / `#[AuditIgnore]`(TARGET_PROPERTY) dans `src/Shared/Domain/Attribute/` (Shared, pas Core → aucun module n'a de dépendance circulaire) ; (2) capture `AuditListener` Doctrine sur `onFlush` (lit `UnitOfWork` : insertions/updates/deletions + `getScheduledCollectionUpdates/Deletions` pour le M2M) puis `postFlush` (écrit, swap-and-clear anti-réentrance) ; (3) écriture `AuditLogWriter` sur connexion DBAL dédiée `audit` (hors transaction ORM → survit aux rollbacks) ; (4) lecture `AuditLogProvider` DBAL (pas d'entité ORM) + `DbalPaginator implements PaginatorInterface` (API Platform génère `hydra:view` seul). +- **Connexion DBAL dédiée + `schema_filter`** : restructurer `doctrine.yaml` de connexion unique → `connections: {default, audit}` (même DSN), `default_connection: default`, `schema_filter: '~^(?!audit_log$).+~'` sur `default` (la table n'a PAS d'entité → exclue de `migrations:diff`/`schema:validate`). Le bloc `orm` reste INCHANGÉ (l'EM par défaut se lie à `default_connection`). En `when@test`, propager `dbname_suffix` aux DEUX connexions (sinon `audit` écrit en base dev pendant que l'ORM écrit en test). +- **Table append-only hors ORM** : créée par migration manuelle (squelette via `doctrine:migrations:generate` puis contenu écrit à la main — JAMAIS `migrations:diff`, qui ne voit pas la table). `id uuid` natif PG, `changes JSONB`, `performed_at TIMESTAMP(6) WITH TIME ZONE`. UUID v7 (writer, tri monotone) / v4 (requestId par requête HTTP). `entity_type` au format `module.Entity` (regex `App\Module\\...\` → `core.User`). +- **Marquage scope = entités migrées** : `#[Auditable]` posé sur User/Role/Permission (Core) uniquement ; `#[AuditIgnore]` sur `User.password` ET `User.apiToken` (Lesstime n'a pas de `plainPassword`). Défense en profondeur : `AuditLogWriter::SENSITIVE_KEYS` strippe aussi `password/plainPassword/apiToken/token/secret`. Les entités métier legacy (`src/Entity/*`) seront marquées à leur migration en modules (2.x). + +### Gotchas +- **Tests fonctionnels Lesstime SANS rollback transactionnel** (pas de DAMADoctrineTestBundle) : les entités persistées survivent d'un run à l'autre → violation d'unicité `username`. Convention projet : `uniqid()` OU nettoyage explicite en `setUp()` (`DELETE FROM "user" WHERE username LIKE 'audit\_%'`). Les données d'audit de test se seedent directement via `doctrine.dbal.audit_connection` (DELETE + inserts UUID v7) pour du déterministe. +- **`migrations:diff` génère un fichier jetable** même quand on ne veut que vérifier : toujours supprimer le `Version.php` non suivi créé après un diff de contrôle (`git ls-files --others migrations/`). Une dérive préexistante `messenger_messages` (DROP) pollue le diff — sans rapport, ne pas committer. +- **`/audit-log-entity-types` = ressource item unique, pas une collection** : `Get` API Platform avec `uriTemplate` fixe sans `{id}` → renvoie `{ entityTypes: string[] }` (PAS d'enveloppe hydra `member`). Le service front ne doit PAS passer par `extractHydraMembers` ici (bug livré par le sous-agent E, corrigé en `9b26b43`). `/audit-logs` en revanche est bien une collection paginée hydra. +- **Login en curl = `/login_check` (POST), pas `/api/login`** ; le JWT json_login est capricieux en curl pur (405/cookie). La preuve d'auth faisant autorité reste le test fonctionnel (client `loginUser()`), pas un smoke curl. + +### Time-tracking / orchestration +- **Interdire explicitement aux sous-agents de toucher au MCP lesstime** (timer + statut ticket) : un sous-agent a spontanément créé/stoppé une time entry (1016) alors que le chrono est piloté par la session principale. Ajouter la consigne « NE TOUCHE PAS au time-tracking » dans chaque prompt de sous-agent. Pas de conflit ici (il avait stoppé l'actif avant), mais découpage involontaire.