# SESSION RESUME — formation-hub Acadenice (last update 2026-05-08 post R3.1.c) ## RECAP SESSION 2026-05-07 (lecture obligatoire post-/compact) ### Pivot strategique majeur acte DocAdenice n'est plus un outil metier formation-hub mais un **produit Notion-like generique**. Le bridge a ete refactor (R1) pour supprimer l'ontologie metier (Personne/Formation/Bloc/Module/Attribution/Client/Projet/Tache/Intervention) au profit de routes generiques `/api/v1/tables/*`. Le metier formation-hub vit dans `examples/acadenice-formation-hub/`. ### Memoire perso a jour - `feedback_no_mvp.md` : Corentin refuse les MVP / shortcuts. Production-like des le jour 1. - `user_role.md` : ancien conseil "MVP first" marque OBSOLETE. - `MEMORY.md` index cree. ### Etat des chantiers (commits, ordres chronologique de la session) **Bridge formation-hub (`bridge/`, push origin+selfhost)** : ``` c998c0d R3.1.b SSE realtime stream (Redis Streams pub/sub, Last-Event-ID, heartbeat) — 380 tests 95089c4 R3.1.a views endpoints (GET /views/table/:id + GET /views/:id/data) — 336 tests a79c51e R2.3b bridge accepte JWT HMAC DocAdenice via DOCMOST_APP_SECRET 2ed73fa R1 refactor proxy generique style Notion 0cf6533 Bloc 5 rate limit + cache invalidation cote writes 571f5c3 Bloc 4 OIDC-ready (Authentik JWKS + service tokens) 8b42cbc chore docmost upstream clone + rename setup 022b1ee Bloc 7 webhooks Baserow + Docmost stub (HMAC + idempotence) c4f087b Bloc 6 tests integration adapters via testcontainers ``` Bridge state : 380/380 tests verts (+44 R3.1.a +6 retry/fake-redis, +11 SSE route, +8 baserow-handler SSE), coverage globale ~90% lines, 3 sources d'auth Bearer (brg_*, RS256 Authentik, HS256 DocAdenice). event.ts 100%, event-bus.ts 100% lines/94.44% branches, events.ts 78.23% lines (onError Hono safety net + heartbeat-closed guard non atteignables sans serveur HTTP reel). Thresholds vitest.config.ts mis a jour pour les 3 nouveaux fichiers. **Fork DocAdenice (`docmost/`, gitignored, branche `acadenice/main`, local-only)** : ``` 71c2aba R3.1.c database-view Tiptap extension + renderer table + slash /database + SSE hook (41 tests) 4d8bd25 R2.3a /api/acadenice/permissions/me + frontend hook React Query propre 022add9 R2.2 frontend pages settings RBAC (PermissionMatrix, sidebar, i18n FR+EN) bcd8611 R2.1 backend RBAC dynamique (catalogue 22 perms, 5 roles seed, JWT enrichi) 06c46f7 fix scopes Authentik (groups dans profile, pas un scope standard) 07d0b66 Bloc 4b OIDC client Authentik via openid-client v6.8.2 efa2644 rebrand DocAdenice (titres + emails, identifiants techniques KEEP) ``` ### Ce qui marche end-to-end (en local) - Bridge expose `/api/v1/tables/*` (CRUD generique Baserow) - Frontend DocAdenice `/settings/roles` + matrix permissions + assignation users - JWT DocAdenice enrichi avec `acadenice_permissions[]` au sign - Bridge consume le claim direct (pas de mapping) - 3 modes auth Bearer cohabitent ### Catalogue 22 permissions atomiques (en code TS, fork) ``` pages:read|write|delete|share, space:read|create|write|delete|invite, tables:list|create|write|delete, rows:read|write|delete, attachments:upload|delete, users:invite|write|delete, roles:manage, admin:* ``` ### 5 roles classiques pre-seed (`is_system_role=true`) Owner=`admin:*`, Admin=tout sauf `*:delete` et `roles:manage`, Editor, Member, Guest. ### Suite immediate : R3 — Tiptap node-views Notion-like (5 sous-blocs) - **R3.1** database-view inline (embed une table/kanban/calendar Baserow dans une page) — decoupe : - R3.1.a bridge endpoints views (LIVRE 95089c4, 336 tests) - R3.1.b bridge SSE realtime (events Baserow -> stream DocAdenice, invalidation cache) — LIVRE c998c0d, 380 tests - R3.1.c frontend node-view Tiptap + renderer table (TanStack Table v8) + slash `/database` — LIVRE 71c2aba (41 tests) - R3.1.d frontend renderers kanban (@dnd-kit) + calendar (lib a trancher) + edit inline - R3.1.e Playwright e2e cross-stack (compose dev Postgres+Redis+Baserow+bridge+DocAdenice, scenarios login -> page -> insert database-view -> CRUD inline -> verify SSE update -> RBAC user-side) - **R3.2** backlinks bidirec (page A reference B → B liste les references entrantes) - **R3.3** slash commands custom (declarer ses propres `/foo` extensibles) - **R3.4** dual editor (code raw markdown + WYSIWYG) - **R3.5** graph view (style Obsidian / AFFiNE) — visualise les liens entre pages : - **R3.5.1 backend** : `GET /api/acadenice/graph` qui parse les contenus Tiptap pour extraire wikilinks/mentions, retourne `{ nodes, edges }` avec filtres (space, tag, depth) - **R3.5.2 frontend** : page `/graph` avec lib graph interactif (zoom, pan, drag, click), candidates : `react-force-graph-2d`, `cytoscape.js`, `sigma.js` - Depend de R3.2 (backlinks fournit la data) ### TODO connus non bloquants - Hook `WorkspaceService.create` pour seed live RBAC (actuellement seed au prochain boot) - Audit log mutations role/assignation - Mapping group sync OIDC -> acadenice_role (sync user.groups Authentik vers acadenice_user_role) - Pagination liste roles (assume < 100 / workspace) - Section "Members" dans page detail role - Endpoint admin debug `GET /permissions/me/effective?for=` ### Push pending au fork Quand un fork remote `acadenice` sera cree (Forgejo ou GitHub fork), push toute la branche `acadenice/main` du repo `docmost/` sur ce remote. Aujourd'hui les commits sont local-only. --- ## CHANGELOG R2.3b — Bridge accepte JWT HMAC DocAdenice (mode local sans Authentik) Date : 2026-05-07 Commit local (a pusher manuellement) : voir `git log -1` dans `bridge/`. **Pourquoi** : DocAdenice signe ses JWT en HS256 avec `appSecret`. En local sans Authentik branche, le frontend DocAdenice qui call le bridge directement aurait echoue (le bridge ne savait valider que `brg_*` et JWT RS256 Authentik). R2.3b ajoute un troisieme mode au middleware : valider les JWT HS256/384/512 signes par DocAdenice via `DOCMOST_APP_SECRET`. **Fichiers crees** : - `bridge/src/middleware/docmost-jwt-verifier.ts` (verifier HMAC + helpers `decodeJwtAlg` + `extractDocmostPermissions`) - `bridge/tests/middleware/docmost-jwt-verifier.test.ts` (28 tests unitaires) **Fichiers modifies** : - `bridge/src/lib/config.ts` : 3 nouvelles vars (`docmostAppSecret`, `docmostJwtIssuer` default "Docmost", `docmostJwtAudience`) + helper `isDocmostJwtEnabled()` - `bridge/src/lib/container.ts` : champ `docmostJwt: DocmostJwtVerifier | null`, init si secret >= 32 chars - `bridge/src/middleware/auth.ts` : routing par algo JWT (decode header non verifie -> RSA -> OIDC, HMAC -> DocAdenice). Sources d'auth ajoutees : `docmost-jwt`, `docmost-cookie`. Refactor en helpers internes pour separer la logique attach par mode. - `bridge/src/index.ts` : injecte `ctn.docmostJwt` dans l'app - `bridge/.env.example` : section commentee `DOCMOST_APP_SECRET` / `DOCMOST_JWT_ISSUER` / `DOCMOST_JWT_AUDIENCE` - `bridge/vitest.config.ts` : threshold >= 85% sur `docmost-jwt-verifier.ts` - `bridge/tests/middleware/auth.test.ts` : +14 tests (DocAdenice mode + coexistence Authentik/DocAdenice + algo none rejected) **Quality gates** : - typecheck : OK - lint : OK - tests : 292/292 verts (was 250/250 — +42 tests) - coverage : `docmost-jwt-verifier.ts` 100% lines/funcs/97.87% branches, `auth.ts` 96.35% lines/93.61% branches **Choix techniques** : - `decodeJwtAlg` : decode header non verifie pour router vers le bon mode. Si JWT non decodable -> AUTH_INVALID immediat. Si algo n'a pas de mode actif -> AUTH_INVALID (pas de fallback silencieux). - DocAdenice JWT : pas de mapping `groupsScopesMap` — le claim `acadenice_permissions[]` (R2.1) est la source de verite directe (DocAdenice resout deja tout via son RBAC). `scopes = permissions = acadenice_permissions[]`. - Constant-time : `jose.jwtVerify` utilise `node:crypto.timingSafeEqual` pour comparaison HMAC. - Algo none / ES* / EdDSA explicitement rejetes (`AUTH_INVALID`) — seuls RS* (mode 2) et HS* (mode 3) routent quelque part. - Validation claims requis dans le verifier : `sub`, `workspaceId`, `type` doivent etre presents et non vides. --- # SESSION RESUME — formation-hub Acadenice (last update 2026-05-07 R1 refactor) ## Vision — DocAdenice = Notion-like generique **Pivot strategique 2026-05-07** : DocAdenice n'est plus un outil metier formation-hub mais un **produit Notion-like generique**. Le bridge est livre vide a un user (admin), qui cree ses tables Baserow comme il veut via UI ou API. Le code n'a aucune ontologie metier. Composants cibles : - **Pages / Spaces** : Docmost reskin (DocAdenice fork) + spaces hierarchiques - **Databases custom** : tables Baserow exposees via le bridge `/api/v1/tables/*` - **RBAC dynamique** : roles custom declares cote DocAdenice qui projettent dans le JWT le claim `acadenice_permissions[]` - **Bidirec backlinks** : pages <-> rows + mentions cross-page (R3) - **Slash commands custom** : Tiptap node-views (R3) - **Dual editor** : edition wiki Docmost + edition table Baserow inline (R3) Le metier formation-hub (CFA + Agence Acadenice) devient un **exemple parmi d'autres** : `examples/acadenice-formation-hub/`. ## CHANGELOG depuis derniere update (R1 — refactor proxy generique style Notion) - **R1 livre (suppression domain metier + bridge generique)** : - Supprime tout le metier formation-hub du bridge : - 9 entites domain (Personne, Formation, Bloc, Module, Attribution, Client, Projet, Tache, Intervention) - types.ts (Role, StatutPersonne, etc.) - baserow-repo.ts (le mega-fichier 554 LOC avec 9 repos heritant de BaseRepo) - 6 routes metier (personnes, formations, projets, modules, interventions, attributions) - Tous les tests metier correspondants (10 tests domain + 6 tests routes + 1 test repo + 1 test rate-limit-app metier) - Cree le domain generique : - `domain/table.ts` (Table : id, name, databaseId, fields[], orderIndex) - `domain/row.ts` (Row : id, tableId, fields opaque, createdOn, updatedOn, order) - `domain/field.ts` (Field : id, name, type libre, primary, options nullable) - `domain/view.ts` (View : id, name, type, tableId) - `domain/schemas.ts` refonte zod (TableSchema, RowSchema, FieldSchema, ViewSchema, RowFieldsSchema permissif) - Cree les repos generiques : - `repos/baserow-tables-repo.ts` (list/get tables — JWT requis upstream) - `repos/baserow-rows-repo.ts` (CRUD rows par tableId — DB token OK) - `repos/baserow-fields-repo.ts` (list fields par tableId — DB token OK) - `repos/baserow-views-repo.ts` (list views + runGrid — DB token OK) - Cree la route generique unique : - `routes/tables.ts` avec 9 endpoints REST : `GET /tables`, `GET /tables/:id` (+ fields embarques), `GET /tables/:id/fields`, `GET /tables/:id/views`, `GET /tables/:id/views/:viewId/rows`, `GET /tables/:id/rows`, `GET /tables/:id/rows/:rowId`, `POST /tables/:id/rows`, `PATCH /tables/:id/rows/:rowId`, `DELETE /tables/:id/rows/:rowId` - Scopes generiques : `read:tables`, `write:tables`, `admin:*` - 501 NOT_IMPLEMENTED si DB token sur endpoint qui exige JWT (list/get tables metadata) - Etendu `BaserowClient` : `listTables`, `getTable`, `listFields`, `listViews`, `getGridViewRows` - Refactor `middleware/auth.ts` : - Supprime entierement le lookup `personneRepo.findByEmail` + cache Personne par email - Supprime `strictMapping` (plus de notion d'email orphelin) - Lit le claim JWT `acadenice_permissions[]` directement dans `extractPermissions(payload)` - `AuthenticatedUser.scopes` = union (groups -> scopes) + (permissions claim) - Plus de `roles[]` dans `AuthenticatedUser` — remplace par `permissions[]` - Refactor `middleware/scopes.ts` : - Supprime `DEFAULT_ROLE_SCOPES` (plus de mapping role formation-hub) - `computeOidcScopes(groups, permissions, groupsMap)` — la signature change - Refactor `webhooks/baserow-handler.ts` : - Plus de cascade rollup metier (attribution -> module + personne, etc.) - Pour chaque event Baserow sur `tableX` : invalide uniquement `bridge:tables::list:*`, `bridge:tables::views:*`, `bridge:tables::row:` (si update/delete) - Si l'utilisateur veut des cascades cross-table, il les pose en formules/lookups Baserow qui emettent leurs propres webhooks naturellement - Refactor `lib/cache.ts` : - `invalidateEntity(redis, entity, id?)` -> `invalidateTable(redis, tableId, rowId?)` - Patterns : `bridge:tables::*` (plus de pattern par entite metier) - Refactor container : - Supprime `tableIds` field (plus de mapping name->id metier) - `RepoSet` = `{ tables, rows, fields, views }` (4 repos generiques) - Supprime `pickTableIds` + `resolveTableIds` au boot (plus necessaire) - Refactor config : - Supprime `authStrictMapping` (plus de Personne lookup) - `BASEROW_TABLE_IDS` env retire (plus de mapping metier) - `.env.example` reecrit : scopes generiques, plus de mention formation-hub - Sortie metier vers exemples : cree `examples/acadenice-formation-hub/` avec README.md, seed-baserow.md (schema 9 tables markdown), example-roles.md (Formateur, Developpeur, Direction, Support, Admin avec permissions generiques) - Tests : 250/250 verts (depuis 319/319). 33 tests metier supprimes ; 33 tests generiques ajoutes (4 domain : table/row/field/view, 4 repos generiques, 19 routes /tables, edge cases, errors helpers, http helpers, isOidcEnabled). - Coverage globale : 89.54% lines / 92.42% branches. - domain/** : 98.9% lines / 93.75% branches (>= 80% ✓) - adapters/** : 89.04% lines / 95.04% branches (>= 70% ✓) - middleware/auth.ts : 97.08% lines / 92% branches (>= 85% ✓) - middleware/rate-limit.ts : 100% (>= 85% ✓) - lib/cache.ts : 100% (>= 85% ✓) - webhooks/** : 100% (>= 80% ✓) - Quality gates verts : `typecheck`, `lint`, `test`, `test:coverage`. ## Status R1/R2/R3 | Bloc | Status | Detail | |------|--------|--------| | **R1 — Bridge refactor proxy generique style Notion** | DONE | Suppression domain metier + nouvelles routes /api/v1/tables/* | | R2 — RBAC dynamique cote DocAdenice (claim `acadenice_permissions[]`) | TODO | docmost-fork-dev | | R3 — Bidirec backlinks + slash commands + dual editor | TODO | Phase 3 | ## CHANGELOG anterieur (Bloc 5 — rate limit + cache invalidation cote writes) - **Bloc 5 livre (rate limit defensif + invalidation cache writes)** : - Nouveau module `src/middleware/rate-limit.ts` : middleware Hono autour de `RedisCache.checkRateLimit` (sliding window deja teste integration). Cle derivee de l'identite avec priorites : `tokenId` (service token) > `email` OIDC (lower-cased) > `sub` OIDC > IP via `x-forwarded-for` (avec WARN log car spoofable) > `anonymous`. Throw `errors.rateLimited(windowSeconds)` avec headers `X-RateLimit-Limit/Remaining/Reset`. Helper exporte `defaultRateLimitKey` pour composer (`${default}:mut`). - Nouveau module `src/lib/cache.ts` : `invalidateEntity(redis, entity, id?)` qui mirror la logique cascade de `webhooks/baserow-handler.ts` (attribution -> module + personne, intervention -> tache + personne, etc.). Volontairement duplique plutot qu'extrait commun car les contextes sont differents (event_type webhook vs intent route). - Wire `src/index.ts` : - `rateLimit(redis, {global})` sur `/api/v1/*` apres l'auth middleware. - `rateLimit(redis, {mutation, keyFrom: ...:mut})` ajoute conditionnellement sur POST/PATCH/PUT/DELETE — compteur Redis distinct, plus strict. - **Pas de rate limit** sur `/api/health`, `/api/ready`, `/api/webhooks/*` (ces dernieres ont HMAC + idempotence Redis qui couvrent). - Routes mutation appellent `invalidateEntity()` apres write reussi (3 routes : `POST /modules/:id/attribuer`, `POST /interventions`, `PATCH /attributions/:id/heures-realisees`). Ferme la fenetre stale entre l'ecriture Baserow et l'arrivee du webhook (idempotent avec l'invalidation webhook qui suivra). - Config zod : 4 vars ajoutees avec defauts `100/60s` global et `30/60s` mutation. Toutes coercees + optionnelles via env (`RATE_LIMIT_GLOBAL_MAX`, etc.). - `.env.example` : section rate limit reecrite (commentee, defauts documentes), ancienne triple-var Phase 1 supprimee. - Tests : **29 tests ajoutes** (290 -> 319). 11 tests rate-limit middleware (cles, priorites, 429, headers, mutation independance, anonymous fallback), 11 tests cache helper (cascade par entite, idempotence, total returned), 7 tests integration `/api/v1/*` vs health/webhooks. Pas de fake timers : `RedisCache.checkRateLimit` est mocke, on simule la reset par mutation du compteur fake (equivalent fonctionnel). - Coverage : `src/middleware/rate-limit.ts` = **100% lines / 100% branches / 100% funcs**. `src/lib/cache.ts` = **100% lines / 100% branches / 100% funcs**. Coverage globale 87.7% (+0.3pt). - vitest.config.ts thresholds : ajout `src/middleware/rate-limit.ts` et `src/lib/cache.ts` a 85%. # SESSION RESUME — formation-hub Acadenice (Bloc 4 — auth OIDC-ready) ## CHANGELOG (Bloc 4 — auth OIDC-ready) - **Bloc 4 livre (middleware OIDC-ready, dual mode)** : - Nouveau module `src/middleware/oidc-verifier.ts` : verification JWT via `jose` + JWKS remote (cache 10min). Algorithmes acceptes : RS256/RS384/RS512 (pas HS* puisque cle publique). Throw `errors.authInvalid()` sur tout echec (signature, expired, issuer mismatch, audience mismatch). - Nouveau module `src/middleware/scopes.ts` : `parseGroupsScopesMap` (parse JSON env var) + `computeOidcScopes` (union groups Authentik + roles formation-hub). Defaut role-scope conservateur (admin -> `admin:*`, formateur -> `read:personnes,read:formations,write:attributions`, etc.). - Refactor `src/middleware/auth.ts` (mais service tokens 100% retro-compat) : - Schemes acceptes : `Authorization: ApiKey brg_*`, `Authorization: Bearer brg_*` (service token) OU `Authorization: Bearer ` OU cookie `authToken=` (OIDC). - Si OIDC desactive (vars Authentik manquantes) + JWT envoye -> 401 (pas de fallback silencieux). - Lookup `PersonneRepo.findByEmail` (nouvelle methode) + cache Redis 60s avec key `bridge:auth:personne-by-email:` (RGPD : pas d'email en clair dans Redis). Cache positif et negatif. - Type `AuthenticatedUser` injecte dans Hono context : `{ source, tokenId?, email?, sub?, personneId?, roles, groups, scopes }`. - Mode strict (defaut) : email orphelin (JWT valide mais pas de Personne) -> 403 FORBIDDEN. Mode permissif : autorise avec scopes des groups uniquement. - `requireScope` etendu : wildcard prefix (`read:*` couvre `read:personnes`, etc.) + admin:*. - Config zod (`src/lib/config.ts`) : ajout `authentikIssuer`, `authentikJwksUri`, `authentikAudience`, `authGroupsScopesMap`, `authStrictMapping` (toutes optionnelles). Helper `isOidcEnabled()` retourne true ssi 3 vars Authentik set. - `PersonneRepo.findByEmail(email)` : recherche via `search` Baserow puis filtre exact post-fetch (case-insensitive + trim). Retourne null sur miss/row-malformee (vs throw). - Erreurs : ajout code `FORBIDDEN` (vs `FORBIDDEN_SCOPE` deja existant) pour les cas auth-mais-pas-de-droits-metier. - Container DI : ajout `oidc: OidcVerifier | null` + `groupsScopesMap`. Construit au boot si vars set. - Tests : **30 tests ajoutes** (260 -> 290). 27 tests integration auth middleware (12 cas reglementaires + 15 cas annexes), 10 tests scopes mapping, 5 tests `findByEmail`. Mini serveur HTTP local genere une cle RSA via `jose.generateKeyPair` -> sert un JWKS reel -> verifier le tape via fetch (plus realiste que mocker `createRemoteJWKSet`). - Coverage `src/middleware/auth.ts` = **94.11% lines / 88.37% branches** (seuil >= 85% applique dans `vitest.config.ts`). Coverage globale stable a 87.38%. - `.env.example` enrichi (commente, prefixe `# AUTHENTIK_*`). `.env` local inchange — Authentik n'est pas branche en local pour l'instant, le mode reste service-tokens-only par defaut. - Lib ajoutee : `jose@^6.2.3` (standard moderne OIDC, ESM-first). # SESSION RESUME — formation-hub Acadenice (last update 2026-05-07 nuit Bloc 7) ## CHANGELOG depuis derniere update (Bloc 7 — webhooks) - **Bloc 7a livre (Baserow webhooks complet)** + **Bloc 7b stub (Docmost)** : - Nouveau module `src/webhooks/` : `signature.ts` (HMAC-SHA256 hex constant-time + format `sha256=` accepte), `types.ts` (zod payloads Baserow + Docmost), `baserow-handler.ts` (mapping table_id -> entite + invalidation cache + cascade rollups parents), `docmost-handler.ts` (stub log-only avec TODO Bloc 8). - Route `POST /api/webhooks/baserow` (HMAC `X-Baserow-Signature`, idempotence Redis 24h, dispatch invalidation par event_type, table inconnue -> 200 ignored). - Route `POST /api/webhooks/docmost` stub (HMAC `X-Docmost-Signature`, idempotence si event_id present, log + 200). - Body brut via `c.req.text()` puis `JSON.parse` manuel (stream consomme une seule fois). - Config zod : `docmostWebhookSecret` ajoute (optional, min 16 chars). - 40 tests Vitest ajoutes (220 -> 260) : signature 13 / handler baserow 10 / handler docmost 2 / routes 15. - Coverage `src/webhooks/**` = **100% lines/branches/funcs**, `src/routes/webhooks.ts` = **97.77% lines / 96.42% branches**. - vitest.config.ts thresholds : ajout `src/webhooks/**` a 80%. # SESSION RESUME — formation-hub Acadenice (Bloc 6, conserve pour reference) > Document de reference pour reprendre le travail apres restart Claude Code OU /compact. > Lis-moi avant de commencer la prochaine session. ## CHANGELOG depuis derniere update (session 2026-05-07 nuit — Bloc 6) 5 commits ajoutes (`5b2abbc`, `2c5665b`, `c8e9b4d`, `7a3fbe4`, `1528017`) — bridge passe de "scaffold + 4 agents recrutes" a "service utilisable end-to-end + adapters couverts a 97-100%" : - **Bloc 1 cloture** (`5b2abbc`) : adapters propres (TS errors fixed, biome format), 679 LOC. - **Bloc 2 livre** (`2c5665b`) : domain models 12 fichiers (Personne, Module, Attribution, Tache, etc.) + 111 tests Vitest, coverage **97.86%** lines sur `src/domain/`. Decimal.js partout pour heures, schemas zod, RG-01 implementee dans Module.creerAttribution. - **Bloc 3 livre** (`c8e9b4d`) : routes REST Tier 1 + auth middleware + repos Baserow + tests integration mockes. **10/10 endpoints livres** : GET personnes/:id/dashboard, GET formations/:id, GET projets/:id, POST modules/:id/attribuer, POST interventions, PATCH attributions/:id/heures-realisees, etc. Tests : **163/163 verts**, coverage globale `src/` : **70.77%**. - **Smoke test fixes** (`7a3fbe4`) : 2 bugs decouverts via test live contre Baserow + Docmost reels : - `BaserowClient.resolveTableIds` requiert un JWT user (Baserow API distingue DB tokens / JWT). Workaround : env var `BASEROW_TABLE_IDS` JSON override. - `BaseRepo.list` cassait sur row malformee (Personne avec splits null != 100 → throw). Fix : try/catch toDomain par row, skip + log warn + `meta.skipped` exposed. - **Bloc 6 livre** (`1528017`) : tests integration des 3 adapters via bridge-tester. **59 nouveaux tests** (220/220 verts au total) : - `redis-cache.test.ts` : 16 tests via testcontainers redis:7-alpine, **100% lines / 95.2% branches**. - `baserow-client.test.ts` : 18 tests via faux serveur node:http local, **99% lines / 96.9% branches**. - `docmost-client.test.ts` : 25 tests via faux serveur node:http (login + cookie + envelope `{data}`), **97.7% lines / 93.7% branches**. - Choix technique : faux serveur HTTP plutot que container Baserow/Docmost (boot 60-120s incompatible CI rapide). Le code adapter tape un vrai socket TCP via ofetch/fetch — boundary integration rigoureux. Helper reutilisable `tests/helpers/http-server.ts`. - vitest.config.ts : threshold 70% lines+branches ajoute sur `src/adapters/**`. - Note design : `RedisCache.checkRateLimit` utilise `${Date.now()}` comme membre ZSET → collision si plusieurs appels dans la meme ms. Workaround dans tests (delay 2ms). Pas critique en prod (charge plus diffuse) mais a noter. ## Smoke test live — etat actuel Stack live + bridge testes : - Baserow : `http://localhost:8080` (workspace 112 "Acadenice", database 133 "formation-hub", 9 tables au singulier 609-617) - Docmost : `http://localhost:3000` - Redis bridge dedie : container `bridge-redis` sur `127.0.0.1:6379` (separe du `docmost-redis` interne) - Bridge : `http://localhost:4000` via `npm run dev` dans `bridge/` `.env` du bridge cree (gitignore confirme). Token Baserow DB cree : `vyabYuYW7E5BLTTV7RGbl2Y0Mkk4hvHP` (workspace 112, CRUD complet). Token bridge admin de test : `brg_smoketest_admin` avec scope `admin:*`. 7/8 endpoints OK au smoke test (le 8e bug est fix dans `7a3fbe4`). Tableau detaille : | Endpoint | Resultat | |----------|----------| | GET /api/health | 200 | | GET /api/ready | 200 (baserow:true, redis:true) | | GET /personnes (no auth) | 401 AUTH_REQUIRED | | GET /personnes (bad token) | 401 AUTH_INVALID | | GET /personnes (good, 2 rows malformees) | 200 data:[] meta.skipped:2 (apres fix) | | GET /personnes/9999 | 404 NOT_FOUND | | GET /formations | 200 (2 rows) | | GET /projets | 200 (2 rows) | ## Etat des blocs Phase 2 (a jour) | Bloc | Status | Detail | |------|--------|--------| | 1 — Adapters | DONE | `5b2abbc`, coverage adapters 97-100% via Bloc 6 | | 2 — Domain models | DONE | `2c5665b`, 97.86% coverage | | 3 — Routes Tier 1 + auth + repos | DONE | `c8e9b4d`, 10/10 endpoints, 86-96% coverage middleware/routes | | 3.2 — Refactor erreurs domain typees + routes /blocs /clients /taches | TODO | DomainError sub-classes (RGViolationError, ConflictError) pour remplacer mapping par texte | | 4 — Auth middleware OIDC-ready | DONE | dual mode service-token + Authentik JWT/cookie, mode OIDC desactive en local (vars env absentes), 27 tests coverage 94.11% | | 4b — Docmost OIDC fork (rebrand DocAdenice + login Authentik) | TODO | docmost-fork-dev — depend Bloc 4 | | 5 — Rate limit + cache invalidation | DONE | middleware/rate-limit.ts (100%), lib/cache.ts (100%), wire global + mutation sur /api/v1/*, invalidation 3 routes write | | 6 — Tests integration adapters | DONE | `1528017`, 59 tests, redis-cache 100% / baserow 99% / docmost 97.7% lines | | 7a — Webhook Baserow (HMAC + idempotence + invalidation cache) | DONE | webhooks/* + routes/webhooks.ts, 100% coverage | | 7b — Webhook Docmost (stub) | STUB | log-only, handlers metier en Bloc 8 | | 7 — Sync bidirec (write-back Baserow apres event Docmost) | TODO | depend de Bloc 8 (parser node-views) | | 8 — Tiptap node-views Docmost | TODO | docmost-fork-dev, Phase 2.3+ — PROCHAIN | | 9 — Bidirec backlinks | TODO | docmost-fork-dev, Phase 3 | | 10 — Doc utilisateur + release v0.1.0 | TODO | tech-writer + acadenice-devops | ## Coverage globale (post-Bloc 7) - **All files** : 87.38% lines / 86.31% branches (post-Bloc 4) - **adapters/** : 98.73% lines / 95.04% branches - **domain/** : 97.86% lines / 98.16% branches - **routes/** : 96.58% lines / 76.19% branches (incluant webhooks.ts 97.77%) - **webhooks/** : **100% lines / 100% branches** (signature, baserow-handler, docmost-handler, types) - **middleware/** : 92.12% lines / 85.36% branches (auth.ts 94.11/88.37, oidc-verifier.ts 88.88/58.33, scopes.ts 100/95.45) - **lib/** : 49.18% lines (config.ts/container.ts non couverts — bootstrap) - **repos/** : 59.53% lines (BaseRepo abstract — couvert via repos concrets) ## Vote pour la prochaine session Recommandation pour la reprise : - **Option A** : DocAdenice rebrand + Bloc 4b — fork Docmost pour ajouter login Authentik + theme Acadenice. Le bridge cote serveur est pret (Bloc 4 livre), reste a brancher Authentik live + faire en sorte que Docmost emette le cookie `authToken` ou un Bearer JWT vers le bridge. - **Option B (recommandee si pas d'Authentik live)** : Bloc 8 — Tiptap node-views Docmost (docmost-fork-dev). Forke Docmost AGPL, ajoute les nodes custom `baserow-row` / `baserow-list` qui font des reads via le bridge `/api/v1/*` et des writes via webhooks Docmost (handler stub deja en place — il restera a parser le payload reel et appeler les repos Baserow). Cest la piece UI manquante qui rend le bridge visible cote utilisateur. - **Option B** : Bloc 9 — tests E2E Playwright contre la stack live (bridge-tester) pour figer le comportement actuel des routes + webhooks avant que le fork Docmost ne bouge. - **Option C** : Bloc 5 — rate limit + cache invalidation middleware. Court (~1h). RedisCache.checkRateLimit existe deja, faut le wire dans Hono. Pas bloquant pour Bloc 8. - **Option D** : Bloc 3.2 — refactor erreurs domain typees + routes restantes (/blocs, /clients, /taches). Pas urgent. ## Vision projet en 3 lignes Notion-like self-host pour Acadenice (CFA + Agence dev) en Stack composite : - **Docmost** (wiki AGPL, illimite users) + **Baserow** (DBs MIT, illimite users) + **bridge service** custom Node TS (Phase 2) - Suivi heures formateurs/devs unifie via entite **PERSONNE** pivot multi-roles, scope etendu CFA + Agence approuve. - Cible 90-100 users total. Production-like des le jour 1. ## User & equipe - **Corentin JOGUET** (corentin@acadenice.fr) — AdminSys/DevOps solo, bras droit de Yan (resp tech). Decisionnaire technique. - Yan (resp tech), Ludo (fondateur), Sophie (co-fondatrice) — validation business. Pas a confondre avec Corentin. ## Localisation des artefacts | Resource | Chemin / URL | |----------|--------------| | Repo source of truth | https://git.acadenice.com/AcadeNice/Wiki (Forgejo selfhost, public) | | Repo mirror GitHub | https://github.com/AcadeNice/wiki (private, plan free) | | Local dev | `/home/imugiii/Documents/jsap/formation-hub/` | | Wiki conception (19 docs) | https://wiki.acadenice.com/collection/agence-rd-notion-like-v9nvBLodst | | BYAN web project | id `4e72108b-dc05-4938-a1a9-530e1551ed52` | | Stack locale | http://localhost:3000 (Docmost) + http://localhost:8080 (Baserow) | ## Phase 0 — Conception (DONE — 19 docs) Localises dans `docs/` du repo + miroir Outline collection R&D Notion-Like : | # | Doc | Status | |---|-----|--------| | 01 | Discovery Recap | OK | | 02 | Scope etendu CFA + Agence (APPROVED 2026-05-07) | OK | | 03 | Decision Records (5 ADR) | OK | | 04 | CDC Technique (stack + NFR + roadmap + couts) | OK | | 05 | Data Dictionary | OK | | 06 | Merise MCD (5 vues splittees) | OK | | 07 | Merise MLD (5 vues splittees) | OK | | 08 | Merise MCT | OK | | 09 | Merise MOT | OK | | 10 | State Diagrams | OK | | 11 | UML Use Cases (4 vues splittees) | OK | | 12 | UML Class Diagram (5 vues splittees) | OK | | 13 | UML Activity Diagrams | OK | | 14 | Repo Structure & GitOps | OK | | 15 | Baserow MPD | OK | | 16 | Plan de tests | OK | | 17 | Plan de deployment | OK | | 18 | Plan d'operations | OK | | 19 | Bridge API design (incl. MCP server Phase 3+) | OK | | 99 | DRAWIO Architecture infra (XML) | OK | ## Phase 1 — Build local (en cours — local seul, prod-like) ### OK et teste live | Iteration | Detail | |-----------|--------| | **I1 — Baserow tables + liens** | 9 tables (PERSONNE + CFA + Agence) + 10 link FK avec related fields renommes | | **I2 — Baserow formulas** | 17 formulas (rollups + heures_restantes) | | **I3 — Docmost setup** | Workspace `Acadenice` + 3 spaces (CFA, Agence, Interne) + page Welcome + share link | | **I5a — Healthcheck etendu** | UI + API Docmost/Baserow + container status — 4/4 OK | ### Partiellement OK | Item | Probleme | Fix prevu | |------|----------|-----------| | **I4a — Forms publics Baserow** | Form cree mais endpoint `/api/database/views/form/{id}/field-options/` retourne 404 sur Baserow 1.30 | A investiguer (URL exacte selon version) — bridge-dev | | **I4b — Space etudiant Docmost** | Slug fix applique (`re.sub`), limit fix (200→100). Pas re-teste. | Re-run pour confirmer — quick | | **I5b — Cron install** | Script ecrit non-execute (sudo requis) | A run sur la prod quand VPS sera up — acadenice-devops | | **I5c — Backup test E2E** | Script `scripts/backup.sh` existant, pas teste end-to-end avec restore | Test mensuel selon plan ops — acadenice-devops | ### TODO Phase 1 finale | Item | Pour qui | |------|----------| | Test rollups Baserow live (1 personne + 1 formation + 1 attribution) | Corentin | | Migration data initiale (formations/clients existants) | Corentin + Yan + Sophie | | Onboarding 5-10 testeurs (Yan, Ludo, Sophie + 2-3 formateurs + 2 devs) | Corentin | | Setup VPS staging (Hetzner CPX21) | acadenice-devops | | Configurer Forgejo Actions runner (`infra/forgejo-runner/`) | acadenice-devops | ## Phase 2 — Bridge service (en cours, Blocs 1-3 + fix smoke test livres) Code Phase 2 = MAIN focus de la session 2026-05-07 soir + suite. Brief complet dans `docs/19-bridge-api-design.md`. Architecture : - 5 missions : expose Baserow, webhooks Baserow, sert Tiptap nodes Docmost, orchestre workflows metier, **sync bidirec Docmost ↔ Baserow** - Stack fixee : Node 22 + Hono + zod + ofetch + ioredis + pino + decimal.js + Vitest + Biome - Endpoints REST `/api/v1/*` versionnes (10 livres Tier 1) - Webhooks anti-loop via header `X-Bridge-Origin` - MCP server (Phase 3+) co-located dans le meme service Cf section "Etat des blocs Phase 2 (a jour)" en haut du document pour le status detaille de chaque bloc. ## Agents BYAN crees pour le projet Dans `.claude/agents/` du repo : | Agent | Mission | Quand l'invoquer | |-------|---------|------------------| | **bridge-dev** | Code TS du bridge service (adapters, domain, routes, webhooks) | Toute tache code metier dans `bridge/` | | **bridge-tester** | Tests Vitest + testcontainers + E2E Playwright + coverage | Toute tache test bridge ou validation AC | | **acadenice-devops** | Infra (Docker, Traefik, Forgejo, backups, monitoring, CI/CD) | Toute tache ops/deploy/infra | | **docmost-fork-dev** | Fork Docmost + Tiptap node-views React + bidirec backlinks | Phase 2.3+ et Phase 3 (UI custom) | **Invocation** : Agent tool avec `subagent_type='bridge-dev'` (ou autre nom) apres restart Claude Code. Chaque agent a un brief detaille (~150-200 lignes) avec : - Mission + contexte projet - Stack technique fixee - Specialisations - Conventions code & commits - Limites (ce qu'il ne fait PAS) - Resources & references ## Workflows BYAN proposes (a creer plus tard) Pas encore crees. A faire via BYAN web ou skill `byan-bmb-workflow-builder` : | Workflow | Phases | |----------|--------| | **WF formation-hub BUILD** | story → bridge-dev code → bridge-tester tests → user review → push → deploy staging → smoke tests. Boucle si fail. | | **WF formation-hub SYNC** | webhook Baserow → bridge handler → cache invalidation → notif → log audit. Idempotence event_id. | | **WF formation-hub RELEASE** | tests E2E staging → CHANGELOG update → tag semver → approval review → deploy prod → 30 min watch period → rollback si fail. | ## Decisions structurelles (a respecter) | Decision | Reference | |----------|-----------| | Stack Docmost + Baserow + bridge custom | ADR-001 doc 03 | | Path B : UX quasi-unified via Tiptap nodes | ADR-002 doc 03 | | Monorepo trunk-based development | ADR-003 doc 03 | | Postgres separe par service | ADR-004 doc 03 | | Bridge stack Node 22 + Hono | ADR-005 doc 03 | | Scope etendu CFA + Agence via PERSONNE pivot | ADR-006 doc 02 | | Etudiants pas modelises en Baserow, juste users Docmost | doc 02 | | API Docmost = Enterprise paye → on utilise endpoints internes (AGPL legal) | doc 19 | | Repo source of truth = Forgejo selfhost (git.acadenice.com), GitHub mirror optionnel | doc 14 | | **Pas de mirror auto** decide pour l'instant | session 2026-05-07 | | **Local seul** pour le moment (pas de staging deploy) | session 2026-05-07 | | Pas de modification des docs conception sans ADR | session 2026-05-07 | ## Credentials utilises (dans `.env` gitignore — a regenerer si compromis) Racine `.env` : ``` DOCMOST_ADMIN_EMAIL=corentin@acadenice.fr DOCMOST_ADMIN_PASSWORD=ton-pwd123456 BASEROW_EMAIL=admin@acadenice.fr BASEROW_PASSWORD=ton-pwd123456 GITHUB_TOKEN=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 (a revoke apres session) FORGEJO_TOKEN=cc21fee2913b6043fb68f93d8b6c184fac4671f4 (admin AcadeNice) OUTLINE_TOKEN=ol_api_s2EqjDW5SPlXzM4vqiZaMd8UD00jsnespK4rRs ``` `bridge/.env` (cree session 2026-05-07 soir) : ``` NODE_ENV=development PORT=4000 LOG_LEVEL=debug BASEROW_API_URL=http://localhost:8080 BASEROW_API_TOKEN=vyabYuYW7E5BLTTV7RGbl2Y0Mkk4hvHP DOCMOST_API_URL=http://localhost:3000 REDIS_URL=redis://127.0.0.1:6379 BASEROW_WEBHOOK_SECRET=smoke-test-webhook-secret-32chars-min BRIDGE_API_TOKENS=[{"token":"brg_smoketest_admin","name":"smoketest","scopes":["admin:*"]}] BASEROW_DATABASE_ID=133 BASEROW_TABLE_IDS={"personne":609,"formation":610,"bloc":611,"module":612,"attribution":613,"client":614,"projet":615,"tache":616,"intervention":617} ``` Container Redis dedie pour bridge (separe du docmost-redis interne) : ```bash docker run -d --name bridge-redis -p 127.0.0.1:6379:6379 redis:7-alpine ``` ## Commits Forgejo selfhost (cumulés) Session 2026-05-07 jour : ``` 668576c chore: initial commit (55 files, 7986 insertions, conception complete) d510bdd ops: fix CI run + bump testcontainers + doc 19 sync bidirec 991d172 ops(ci): trigger CI on main + disable auto deploy-staging Phase 0 66ff909 ops(ci): add vitest config + sanity tests d8e8bde ops(ci): fix docker-build .env before compose ecb7a44 ops(infra): add Forgejo Actions Runner skeleton 6724be6 feat(baserow): add seed script + Fast-App iteration 1 artifacts a0266b8 feat(baserow): add formulas pass + related field naming 5d02977 feat(docmost): manual setup guide for iteration 3 8a676d2 feat(docmost): add seed.py via internal endpoints d5558ca fix(docmost-seed): handle data envelope + add format field 1d71364 feat(seed): add I4 forms publics + space etudiant + I5 healthcheck 7d4d2cd feat(agents): create bridge-dev specialized agent (1st BYAN INT) b37220d feat(agents): complete BYAN INT for 3 more agents + session resume MD 460f7ef feat(workflows): create 5 BYAN workflows for agent collaboration ``` Session 2026-05-07 soir : ``` 5b2abbc feat(bridge/adapters): bloc 1 propre — BaserowClient + DocmostClient + RedisCache 2c5665b feat(bridge/domain): bloc 2 — domain models + tests Vitest (coverage 97.86%) c8e9b4d feat(bridge): bloc 3 — routes REST Tier 1 + auth + repos Baserow (10 endpoints) 7a3fbe4 fix(bridge): smoke test fixes — skip rows malformees + BASEROW_TABLE_IDS override [NEXT] docs(session): update SESSION-RESUME apres Bloc 1+2+3 + smoke test ``` ## Memoire BYAN persistee `/home/imugiii/.claude/projects/-home-imugiii-Documents-jsap/memory/` : - `user_role.md` : Corentin JOGUET profil + role chez Acadenice - `project_notion_like.md` : projet detaille, scope, stack, decisions, IDs externes - `reference_outline.md` : Outline wiki Acadenice config + endpoints ## Pour la prochaine session — checklist demarrage ``` [ ] Lire ce SESSION-RESUME.md (ce CHANGELOG en haut + section "Etat des blocs Phase 2") [ ] Verifier stack locale up : docker compose ps + docker ps | grep bridge-redis (si bridge-redis absent : docker run -d --name bridge-redis -p 127.0.0.1:6379:6379 redis:7-alpine) [ ] Verifier git pull a jour : cd formation-hub && git pull [ ] Verifier bridge boot : cd bridge && npm run dev (logs dans /tmp/bridge-smoke.log si en background) [ ] Smoke quick : curl http://localhost:4000/api/ready [ ] Decider quoi attaquer en premier (cf section "Vote pour la prochaine session" en haut) : - Option A : Bloc 7 — webhooks Baserow + sync bidirec (gros, recommande) - Option B : Bloc 5 — rate limit + cache invalidation (court, prerequis) - Option C : Bloc 6 — tests integration adapters via bridge-tester - Option D : Bloc 3.2 — refactor erreurs domain typees + routes restantes ``` ## Fast-App workflow local (artefacts dans `_byan-output/fast-app/formation-hub/`) | Fichier | Contenu | |---------|---------| | pitch.json | Validated 2026-05-07 | | backlog.json | 15 features MoSCoW + 5 WONT validated | | cdcf-stories.json | 10 stories Connextra+Gherkin pour Phase 1 | | plan.json | 7 iterations BUILD | | dispatch.json | Repartition Claude/Corentin/equipe | | build-state.json | current_iteration: 1, completed phases 1-6 (workflow Fast-App) | --- **Tao Acadenice respecte tout au long** : direct, structures avec tirets, zero emoji, orientation solution. Pret pour la suite. Bonne session.