Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
41 KiB
SESSION RESUME — formation-hub Acadenice (last update post R3.5.1)
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.mdindex cree.
Etat des chantiers (commits, ordres chronologique de la session)
Bridge formation-hub (bridge/, push origin+selfhost) :
e969545 R3.1.e Playwright e2e cross-stack (compose+7 scenarios+CI workflow) — R3.1 ENTIEREMENT TERMINE
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) :
5f7271d feat(acadenice): add graph endpoint for R3.5.1 (35 tests, Patch 012)
9be979e feat(acadenice): add dual editor (WYSIWYG + markdown source) for R3.4 (77 tests)
ba18a34 docs(fork): update ACADENICE_PATCHES.md Patch 010 for R3.3
4e2af88 feat(acadenice): add custom slash commands system for R3.3 (183 tests total)
8cd57f9 docs(fork): update ACADENICE_PATCHES.md Patch 009 for R3.2
2fc310a feat(acadenice): add bidirectional backlinks + wikilinks for R3.2 (135 tests total)
ba8d867 test(e2e): add data-testid attributes for Playwright e2e (Patch 008 R3.1.e)
ea00386 docs(fork): update ACADENICE_PATCHES.md Patch 007 for R3.1.d
f3fae2a R3.1.d kanban + calendar renderers + inline edit (33 tests, total 96)
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 23 permissions atomiques (en code TS, fork) — mis a jour R3.3
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,
slash_commands:manage (R3.3 - nouveau), 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 (@fullcalendar) + edit inline — LIVRE f3fae2a (33 tests, total 96)
- R3.1.e Playwright e2e cross-stack (compose dev Postgres+Redis+Baserow+bridge+DocAdenice, 7 scenarios) — LIVRE
e969545(formation-hub) + ba8d867 (fork testids)
- R3.1.a bridge endpoints views (LIVRE
- R3.2 backlinks bidirec (page A reference B → B liste les references entrantes) — LIVRE
2fc310a - R3.3 slash commands custom (declarer ses propres
/fooextensibles) — LIVRE4e2af88 - R3.4 dual editor (code raw markdown + WYSIWYG) — LIVRE
9be979e - R3.5 graph view (style Obsidian / AFFiNE) — visualise les liens entre pages :
- R3.5.1 backend :
GET /api/acadenice/graph— LIVRE5f7271d(35 tests, Patch 012) - R3.5.2 frontend : page
/graphavec lib graph interactif (zoom, pan, drag, click), candidates :react-force-graph-2d,cytoscape.js,sigma.js - Depend de R3.2 (backlinks fournit la data)
- R3.5.1 backend :
- R3.6 templates de pages — creer/sauvegarder/instancier templates incluant database-views, slash customs, structure. Table
acadenice_template+ UI gallery. - R3.7 mentions
@user+ notifs in-app — mention declenche notif + email. Reuse RBAC visibility check. Center notifs UI + bell icon. - R3.8 comments inline — threads sur paragraphes Tiptap + sur rows database. Resolu/non-resolu. Notif R3.7 sur reply.
Mode Loop full autonome (decision 2026-05-08)
Loop autonome jusqu'a fin R3 (R3.1.d -> R3.8). Pas de checkpoint user. Apres chaque sub-bloc : commit + push + update SESSION-RESUME, puis sub-bloc suivant.
TODO connus non bloquants
- Hook
WorkspaceService.createpour 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=<userId>
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 + helpersdecodeJwtAlg+extractDocmostPermissions)bridge/tests/middleware/docmost-jwt-verifier.test.ts(28 tests unitaires)
Fichiers modifies :
bridge/src/lib/config.ts: 3 nouvelles vars (docmostAppSecret,docmostJwtIssuerdefault "Docmost",docmostJwtAudience) + helperisDocmostJwtEnabled()bridge/src/lib/container.ts: champdocmostJwt: DocmostJwtVerifier | null, init si secret >= 32 charsbridge/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: injectectn.docmostJwtdans l'appbridge/.env.example: section commenteeDOCMOST_APP_SECRET/DOCMOST_JWT_ISSUER/DOCMOST_JWT_AUDIENCEbridge/vitest.config.ts: threshold >= 85% surdocmost-jwt-verifier.tsbridge/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.ts100% lines/funcs/97.87% branches,auth.ts96.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 claimacadenice_permissions[](R2.1) est la source de verite directe (DocAdenice resout deja tout via son RBAC).scopes = permissions = acadenice_permissions[]. - Constant-time :
jose.jwtVerifyutilisenode:crypto.timingSafeEqualpour 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,typedoivent 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.tsrefonte 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.tsavec 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 dansextractPermissions(payload) AuthenticatedUser.scopes= union (groups -> scopes) + (permissions claim)- Plus de
roles[]dansAuthenticatedUser— remplace parpermissions[]
- Supprime entierement le lookup
- Refactor
middleware/scopes.ts:- Supprime
DEFAULT_ROLE_SCOPES(plus de mapping role formation-hub) computeOidcScopes(groups, permissions, groupsMap)— la signature change
- Supprime
- Refactor
webhooks/baserow-handler.ts:- Plus de cascade rollup metier (attribution -> module + personne, etc.)
- Pour chaque event Baserow sur
tableX: invalide uniquementbridge:tables:<tableX>:list:*,bridge:tables:<tableX>:views:*,bridge:tables:<tableX>:row:<id>(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:<tableId>:*(plus de pattern par entite metier)
- Refactor container :
- Supprime
tableIdsfield (plus de mapping name->id metier) RepoSet={ tables, rows, fields, views }(4 repos generiques)- Supprime
pickTableIds+resolveTableIdsau boot (plus necessaire)
- Supprime
- Refactor config :
- Supprime
authStrictMapping(plus de Personne lookup) BASEROW_TABLE_IDSenv retire (plus de mapping metier)
- Supprime
.env.examplereecrit : 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.
- Supprime tout le metier formation-hub du bridge :
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 deRedisCache.checkRateLimit(sliding window deja teste integration). Cle derivee de l'identite avec priorites :tokenId(service token) >emailOIDC (lower-cased) >subOIDC > IP viax-forwarded-for(avec WARN log car spoofable) >anonymous. Throwerrors.rateLimited(windowSeconds)avec headersX-RateLimit-Limit/Remaining/Reset. Helper exportedefaultRateLimitKeypour composer (${default}:mut). - Nouveau module
src/lib/cache.ts:invalidateEntity(redis, entity, id?)qui mirror la logique cascade dewebhooks/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/60sglobal et30/60smutation. 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.checkRateLimitest 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.tsetsrc/lib/cache.tsa 85%.
- Nouveau module
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 viajose+ JWKS remote (cache 10min). Algorithmes acceptes : RS256/RS384/RS512 (pas HS* puisque cle publique). Throwerrors.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) OUAuthorization: Bearer <jwt>OU cookieauthToken=<jwt>(OIDC). - Si OIDC desactive (vars Authentik manquantes) + JWT envoye -> 401 (pas de fallback silencieux).
- Lookup
PersonneRepo.findByEmail(nouvelle methode) + cache Redis 60s avec keybridge:auth:personne-by-email:<sha256(email)>(RGPD : pas d'email en clair dans Redis). Cache positif et negatif. - Type
AuthenticatedUserinjecte 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.
requireScopeetendu : wildcard prefix (read:*couvreread:personnes, etc.) + admin:*.
- Schemes acceptes :
- Config zod (
src/lib/config.ts) : ajoutauthentikIssuer,authentikJwksUri,authentikAudience,authGroupsScopesMap,authStrictMapping(toutes optionnelles). HelperisOidcEnabled()retourne true ssi 3 vars Authentik set. PersonneRepo.findByEmail(email): recherche viasearchBaserow puis filtre exact post-fetch (case-insensitive + trim). Retourne null sur miss/row-malformee (vs throw).- Erreurs : ajout code
FORBIDDEN(vsFORBIDDEN_SCOPEdeja 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 viajose.generateKeyPair-> sert un JWKS reel -> verifier le tape via fetch (plus realiste que mockercreateRemoteJWKSet). - Coverage
src/middleware/auth.ts= 94.11% lines / 88.37% branches (seuil >= 85% applique dansvitest.config.ts). Coverage globale stable a 87.38%. .env.exampleenrichi (commente, prefixe# AUTHENTIK_*)..envlocal 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).
- Nouveau module
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 + formatsha256=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(HMACX-Baserow-Signature, idempotence Redis 24h, dispatch invalidation par event_type, table inconnue -> 200 ignored). - Route
POST /api/webhooks/docmoststub (HMACX-Docmost-Signature, idempotence si event_id present, log + 200). - Body brut via
c.req.text()puisJSON.parsemanuel (stream consomme une seule fois). - Config zod :
docmostWebhookSecretajoute (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%.
- Nouveau module
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 sursrc/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 globalesrc/: 70.77%. - Smoke test fixes (
7a3fbe4) : 2 bugs decouverts via test live contre Baserow + Docmost reels :BaserowClient.resolveTableIdsrequiert un JWT user (Baserow API distingue DB tokens / JWT). Workaround : env varBASEROW_TABLE_IDSJSON override.BaseRepo.listcassait sur row malformee (Personne avec splits null != 100 → throw). Fix : try/catch toDomain par row, skip + log warn +meta.skippedexposed.
- 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.checkRateLimitutilise${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-redissur127.0.0.1:6379(separe dudocmost-redisinterne) - Bridge :
http://localhost:4000vianpm run devdansbridge/
.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
authTokenou 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-listqui 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) :
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 Acadeniceproject_notion_like.md: projet detaille, scope, stack, decisions, IDs externesreference_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.