# SESSION RESUME — formation-hub Acadenice (last update Patch 031 — Baserow user JWT auto-login) ## 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)** : ``` 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)** : ``` b53ab50 feat(acadedoc): add AcadeDoc branding, Brevo SMTP preset, UI customization — R4.4 d0b7577 feat(acadenice): add timeline view (Gantt) for databases — R4.1 4cf0408 fix(acadenice): resolve test suite failures across R3 sub-blocks (Patch 017) - 17 server specs converted from vitest to Jest (vi -> jest globals) - jest.mock stubs for ESM-only prosemirror/html and collaboration modules - Zod v4 strict UUID fixtures fixed (version byte [1-8][89abAB] required) - JwtAuthGuard.overrideGuard added to all controller specs - jest.Mock explicit types to prevent 'never' arg inference - Client: deleted vitest.config.ts (CJS), kept vitest.config.mts (ESM) - Client: global mocks for @excalidraw/excalidraw and @/main.tsx - Result: client 38/38 suites 313/313 tests, server acadenice 21/21 suites 210/210 tests, 0 TS errors be951a2 feat(acadenice): add inline comments threads for R3.8 (30 tests, Patch 016) 7d076aa feat(acadenice): add mentions notifications system for R3.7 (45 tests, Patch 015) 614533f feat(acadenice): add page templates system for R3.6 (65 tests, Patch 014) aac0149 feat(acadenice): add graph view UI for R3.5.2 (58 tests, Patch 013) 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 34 permissions atomiques (en code TS, fork) — mis a jour R4.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), templates:read|create|manage (R3.6), comments:read|write|resolve|moderate (R3.8), sync_blocks:create|edit|delete (R4.2), clipper:use (R4.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.2** backlinks bidirec (page A reference B → B liste les references entrantes) — **LIVRE** `2fc310a` - **R3.3** slash commands custom (declarer ses propres `/foo` extensibles) — **LIVRE** `4e2af88` - **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` — **LIVRE** `5f7271d` (35 tests, Patch 012) - **R3.5.2 frontend** : page `/graph` interactif (zoom, pan, drag, click, side panel, search) — **LIVRE** `aac0149` (58 tests, Patch 013) - Depend de R3.2 (backlinks fournit la data) - **R3.5 ENTIEREMENT TERMINE** - **R3.6** templates de pages — **LIVRE** `614533f` (65 tests, Patch 014). Table `acadenice_template`, 5 built-ins seedes, gallery /settings/templates, picker modal sidebar + slash /template, 26 permissions. - **R3.7** mentions `@user` + notifs in-app — **LIVRE** `7d076aa` (45 tests, Patch 015). MentionDetectorService (pure walker), NotificationEmitterService (REST path bridge), AcadeniceNotificationsModule facade, /notifications page, /settings/notifications prefs. Native collab path already active. Poll 30s unread count. No new DB table. - **R3.8** comments inline — **LIVRE** `be951a2` (30 tests, Patch 016). REST resolve facade for page comments (PageCommentResolveService + CollaborationGateway yjs sync). New acadenice_row_comment table + RowCommentService + REST CRUD + React RowCommentsPanel in RowDetailModal tabs. 4 new permissions comments:read|write|resolve|moderate (30 total). i18n FR+EN. - **R3 ENTIEREMENT TERMINE** ### R4.3 — Web Clipper extension navigateur — LIVRE Commit : `23a8526` (co-commit avec R4.2 sync-blocks par pre-commit hook) 32 fichiers clipper, 59 tests verts (36 server Jest + 23 extension Vitest) **Backend** : - `apps/server/src/core/acadenice/clipper/` — ClipperModule, ClipperController, ClipperService, ClipperTokenService - POST /api/acadenice/clipper/import — auth X-Clipper-Token, body Zod (url, title, html_selection?, target_workspace_id, target_space_id, target_parent_page_id?) - HTML -> ProseMirror via `htmlToJson` (Tiptap), source attribution paragraph prepended - Token CRUD JWT : POST/GET/DELETE /api/acadenice/clipper/tokens - ClipperTokenService : bcrypt hash stocke en DB, validate() scan lineaire (< 100 tokens), last_used_at bump async - Migration 20260509T120000 : table `acadenice_clipper_token` (id, user_id, workspace_id, token_hash, label, last_used_at, expires_at) - Permission `clipper:use` ajoutee au catalogue (Admin/Editor/Member) - 36 specs Jest : controller (8), service (5), token.service (9), dto (14) **Extension** : `apps/extension-clipper/` workspace package `@docadenice/extension-clipper` - MV3 Chrome + Firefox, Vite + @crxjs/vite-plugin, TypeScript strict - Popup vanilla TS : onglet Clip (title, space, parent, selection badge, bouton Clip) + onglet Settings (URL, token, workspace/space IDs) - Content script : `GET_PAGE_DATA` message handler, DOMPurify sanitization, canonical URL detection - Background service worker : context menu "Clip to DocAdenice", chrome.action.openPopup() + fallback tab - Helpers testables : `html-extractor.ts`, `api-client.ts`, `storage.ts` - i18n EN/FR : 27 cles, detection auto navigator.language - 23 tests Vitest : api-client (11), html-extractor (9), i18n (3) - README.md : install dev, load Chrome/Firefox, zip production, generate token, securite **Client** : - `apps/client/src/features/acadenice/clipper/` : clipper-client.ts, clipper-query.ts (useClipperTokens/useCreateClipperToken/useRevokeClipperToken), clipper-tokens-page.tsx - Route `/settings/clipper-tokens`, sidebar entry "Clipper tokens" avec IconScissors - 1 test client : clipper-client.test.ts (3 specs) **Install extension** : ```bash npx --yes pnpm@10.4.0 --filter @docadenice/extension-clipper build # Chrome : charger apps/extension-clipper/dist/ # Firefox : about:debugging -> Load Temporary Add-on -> dist/manifest.json ``` **Generer un token** : Settings > Clipper tokens > Generate token -> coller dans extension Settings ### R4.4 — AcadeDoc branding + Brevo SMTP + UI customization (2026-05-08) Commit `b53ab50`, 27 fichiers, 1012 insertions. - BRAND_NAME env var pilote le nom visible (titre HTML, PWA manifest, emails, header) - BRAND_PRIMARY_COLOR / BRAND_ACCENT_COLOR : theming Mantine v7 via brand-theme.ts (10 shades par couleur, generation hex pure sans dep externe) - /settings/branding : page admin pour les couleurs + lien vers General pour le logo - POST /workspace/branding/update : endpoint admin-only avec validation hex (DTO regex) - Migration 20260509T140000 : primary_color + accent_color dans workspaces - transactional/README.md : guide ops Brevo complet (generation SMTP key, STARTTLS port 587, limites free 300/j, swaks/curl test) - Tests : 11 client (brand-theme) + 13 server (workspace-branding) = 24 nouveaux, tous verts - 0 TS errors client + server ### Mode Loop full autonome (decision 2026-05-08) Loop autonome R3.1.d -> R3.8 termine. Patch 017 fix typecheck post-install pnpm. Etat final : 0 TS error client + server, 313 tests client + 210 tests server + 380 tests bridge tous verts. ## R4 progress ### R4.5 — EE Settings replacement: Audit log, API keys, OIDC status — LIVRE Commit : `5b512e6`, 37 fichiers, 2382 insertions, 0 TS errors client + server Tests server : 36 verts (14 audit-log, 16 api-keys, 5 oidc-status) via Jest Client typecheck : 0 erreurs. Client tests : 363 pass (1 pre-existing failure non liee clipper jest.mock/vitest) **Probleme resolu** : le sidebar Settings affichait Security & SSO, API keys, Audit log comme desactives (EE feature gates) pointant vers des pages EE non chargees. Ces entrees sont desormais rewirees vers des pages open source sans feature gate. **Server** : - AcadeniceAuditLogModule : GET /api/acadenice/audit-log (admin/owner, Kysely, pagination offset + filtres userId/action/since/until). Table native `audit`. - AcadeniceApiKeysModule : GET/POST/DELETE /api/acadenice/api-keys (JWT, bcrypt BCRYPT_ROUNDS=10, prefix acdk_, 64 random hex bytes). Table propre `acadenice_api_key` (migration 20260510T100000). Token retourne une seule fois, hash stocke uniquement. - AcadeniceSecurityModule : GET /api/acadenice/security/oidc-status (admin/owner). Retourne {enabled, providerName, issuer, scopes, redirectUri, loginUrl}. Le clientSecret n'est pas inclus dans la reponse (non expose par design du controleur). - permissions-catalog.ts : audit_log:read ajoute. - core.module.ts : 3 nouveaux modules registres. **Client** : - features/acadenice/audit-log/ : types, service (GET /acadenice/audit-log), query, table component (Badge event, tooltip details JSONB tronque), page (filtres Select+TextInput+DatePickerInput, pagination offset, aria-labels). - features/acadenice/api-keys/ : types, service (GET/POST/DELETE), queries (useCreate + useRevoke), 3 modals (create avec duree 30j/90j/1an/no-expiry, token one-time copy, revoke confirm), page liste. - features/acadenice/oidc-status/ : types, service, query, SecurityPage (statut badge, infos issuer/scopes/redirectUri/loginUrl, reference toutes vars OIDC_*, section API keys best practices). - App.tsx : 3 nouvelles routes settings/acadenice/audit-log, settings/acadenice/api-keys, settings/acadenice/security. - settings-sidebar.tsx : rewired Security & SSO -> /settings/acadenice/security (role:admin, sans feature gate), API keys -> /settings/acadenice/api-keys (sans feature gate), Audit log -> /settings/acadenice/audit-log (role:admin, selfhosted, sans feature gate). API management garde son feature gate EE. - settings-queries.tsx : prefetchAcadeniceAuditLogs + prefetchAcadeniceApiKeys ajoutes. **Docs** : docs/security.md cree — OIDC (Authentik/Keycloak/Auth0 examples), API keys (rotation, RGPD), Audit log (events logges, retention SQL, RBAC). **Note Kysely** : DbInterface utilise cles camelCase (workspaceId, actorId, createdAt). Les where clauses sur la table audit utilisent ces cles. Colonnes aliasees du join (actorEmail, actorName) castees via any car absentes du type statique. ### R4.1 — Timeline view (Gantt) — LIVRE Commit : TBD (voir git log -1 dans docmost/) Tests : 26 nouveaux (14 client timeline-renderer + 12 bridge views-r4-timeline) Total client apres R4.1 : 326 tests verts (+ 1 pre-existing clipper failure non liee, uses jest.mock dans Vitest) Total bridge apres R4.1 : 392 tests verts **Deps ajoutees** : - `@fullcalendar/timeline@^6.1.20` (client) - `@fullcalendar/resource-timeline@^6.1.20` (client) **Fichiers crees** : - `apps/client/src/features/acadenice/database-view/renderers/timeline-renderer.tsx` - `apps/client/src/features/acadenice/database-view/renderers/timeline-renderer.module.css` - `apps/client/src/features/acadenice/database-view/hooks/use-timeline-config.ts` - `apps/client/src/features/acadenice/database-view/__tests__/timeline-renderer.test.tsx` - `bridge/tests/routes/views-r4-timeline.test.ts` **Fichiers modifies** : - `bridge/src/routes/views.ts` — 2 nouveaux endpoints (GET+POST /views/:id/timeline-config) + fields dans /data response - `apps/client/src/features/acadenice/database-view/extension/database-view-component.tsx` — dispatch timeline - `apps/client/src/features/acadenice/database-view/slash-command/insert-database-modal.tsx` — step 3 mapping timeline - `apps/client/src/features/acadenice/database-view/types/database-view.types.ts` — "timeline" dans SUPPORTED_VIEW_TYPES **Architecture timeline** : - Bridge Redis TTL 30j pour timeline-config keyed par viewId - POST /views/:id/timeline-config requiert scope write:tables (403 pour read-only tokens) - GET /views/:id/timeline-config requiert scope read:tables - Client hook useTimelineConfig (GET+POST via bridge-client) - Modal 3-step : table -> view -> column-mapping (step 3 visible uniquement pour viewType=timeline) - Renderer : config panel quand pas de config ; FullCalendar Timeline apres config - Resource swimlane automatique quand resourceCol configure - eventResize persiste endCol via useUpdateRow (respecte canWriteRows) - Fallback end = start+1j si endCol absent - Toutes hooks declarees avant early returns (React rules-of-hooks respectees) ### R4.2 — Sync blocks (Notion-style) — LIVRE Commit : `23a8526` Tests : 32 server Jest (controller 13, service 14, repo 5 pure-fn, broadcast 3) + 18 client Vitest (extension 5, node-view 10, insert 3) — tous verts Permissions ajoutees au catalogue RBAC : `sync_blocks:create`, `sync_blocks:edit`, `sync_blocks:delete` (assignees Admin/Editor/Member) 0 TS errors client + server **Architecture** : - Table `acadenice_sync_block` (migration 20260509T100000) : id UUID, workspace_id FK, content JSONB, yjs_state BYTEA, created_by FK users RESTRICT - Hocuspocus `PersistenceExtension` etendue : prefix `sync-block-*` route vers `loadSyncBlockDoc`/`storeSyncBlockDoc` (separate de la persistence pages) - Cycle detection BFS depth 5 via `extractMasterIdsFromProseMirror` + `assertNoCycle` dans `SyncBlocksService` (`ConflictException('CYCLIC_SYNC_BLOCK')`) - SSE broadcast : `SyncBlockBroadcastService` wraps EventEmitter2, hook client `useSyncBlockRealtime` reconnecte avec backoff expo 1s->30s - Collab client : `HocuspocusProvider` pointe sur doc `sync-block-{masterId}`, overlay Mantine Modal **Fichiers crees (server)** : - `apps/server/src/database/migrations/20260509T100000-create-acadenice-sync-block.ts` - `apps/server/src/core/acadenice/sync-blocks/dto/sync-block.dto.ts` - `apps/server/src/core/acadenice/sync-blocks/repos/sync-block.repo.ts` - `apps/server/src/core/acadenice/sync-blocks/services/sync-blocks.service.ts` - `apps/server/src/core/acadenice/sync-blocks/services/sync-block-broadcast.service.ts` - `apps/server/src/core/acadenice/sync-blocks/controllers/sync-blocks.controller.ts` - `apps/server/src/core/acadenice/sync-blocks/sync-blocks.module.ts` - `apps/server/src/core/acadenice/sync-blocks/spec/` (4 fichiers spec) **Fichiers crees (client)** : - `apps/client/src/features/acadenice/sync-blocks/extension/sync-block-extension.ts` - `apps/client/src/features/acadenice/sync-blocks/components/sync-block-node-view.tsx` - `apps/client/src/features/acadenice/sync-blocks/hooks/use-sync-block-realtime.ts` - `apps/client/src/features/acadenice/sync-blocks/services/sync-blocks-client.ts` - `apps/client/src/features/acadenice/sync-blocks/slash-command/insert-sync-block.ts` - `apps/client/src/features/acadenice/sync-blocks/__tests__/` (3 fichiers test) **Fichiers modifies** : - `apps/server/src/collaboration/extensions/persistence.extension.ts` — routing sync-block-* docs - `apps/server/src/collaboration/collaboration.module.ts` — providers SyncBlockRepo + SyncBlockBroadcastService - `apps/server/src/core/acadenice/rbac/permissions-catalog.ts` — 3 perms sync_blocks - `apps/server/src/core/acadenice/rbac/services/seed.service.ts` — assignation roles - `apps/server/src/core/core.module.ts` — import AcadeniceSyncBlocksModule - `apps/client/src/features/editor/extensions/extensions.ts` — SyncBlockExtension dans mainExtensions - `apps/client/src/features/editor/components/slash-menu/menu-items.ts` — slash command /sync-block **Note** : endpoint SSE `/api/acadenice/sync-blocks/:id/events` marque "planned R4.2.b" — hook client se reconnecte gracieusement, invalidation via Hocuspocus store broadcast fonctionnelle. ### Patch 031 — Baserow user JWT auto-login (2026-05-08) Commit : (voir `git log -1 bridge/`) Tests bridge apres Patch 031 : 417 verts (was 392 — +25 tests) Typecheck : 0 erreurs **Probleme resolu** : `GET /api/v1/views/table/:id` retournait 401 PERMISSION_DENIED de Baserow car le DB token (BASEROW_API_TOKEN) ne peut pas appeler les endpoints metadata. **Solution** : service account pattern. Un compte Baserow dedie se connecte via `POST /api/user/token-auth/`, JWT conserve en memoire, refresh auto avant expiry. **Fichiers crees** : - `bridge/src/lib/baserow-jwt-manager.ts` : BaserowJwtManagerImpl (lazy-init, cache, mutex, refresh fallback), BaserowJwtManagerDisabled (creds absentes → 503), factory `createBaserowJwtManager` - `bridge/tests/lib/baserow-jwt-manager.test.ts` : 18 tests unitaires (mock fetch) - `bridge/tests/routes/views-r4-jwt.test.ts` : 7 tests routes views avec JWT manager stub - `bridge/docs/baserow-auth.md` : doc ops (endpoints Baserow, creation user service, vars env, verification) **Fichiers modifies** : - `bridge/src/lib/config.ts` : 3 nouvelles vars (`baserowUserEmail`, `baserowUserPassword`, `baserowJwtRefreshMargin`) - `bridge/src/lib/container.ts` : champ `baserowJwt: BaserowJwtManager`, init au boot, log enabled/disabled - `bridge/src/adapters/baserow-client.ts` : 2 nouvelles methodes `listViewsWithJwt` + `getTableWithJwt` (header `JWT `) - `bridge/src/repos/baserow-views-repo.ts` : `jwtManager` injectable, `listViewsResolved()` route via JWT si dispo, sinon DB token, 503 si unconfigured - `bridge/tests/helpers/test-app.ts` : `baserowJwt` injectable dans test container **Vars d'env a ajouter en prod** : ``` BASEROW_USER_EMAIL=bridge-svc@interne.local BASEROW_USER_PASSWORD= # optionnel BASEROW_JWT_REFRESH_MARGIN=60 ``` **Deploiement** : 1. Creer user Baserow via `/admin/users/` (Is staff: non, Member sur le workspace) 2. Ajouter les 2 vars en prod (Docker secrets / `.env`) 3. Redemarrer le bridge — log "Baserow user JWT manager enabled" confirme 4. Verifier : `curl -H "Authorization: Bearer brg_" http://localhost:4000/api/v1/views/table/personne` ### Questions ouvertes a trancher post-/compact (2026-05-08) **Q1 — Strategie test E2E AI-driven (Claude Code / Stagehand / Computer Use)** - Constat : Playwright (R3.1.e livre) couvre les chemins critiques deterministes. Manque coverage exploratoire. - Option A : ajouter `R3.1.f Claude Code smoke` — workflow CI pre-release qui invoke Claude via Agent SDK avec acces Playwright + scenario en markdown. ~$0.50/run Sonnet, ~$2 Opus. - Option B : decaler en R4.x post-release stabilization. - Position complementaire (pas de remplacement Playwright) : Playwright = CI a chaque push deterministe, Claude smoke = nightly creatif decouverte UX. - Decision a prendre : R3.1.f ou R4.x ? **Q2 — Repartition Postgres DocAdenice vs Baserow (deja livre, audit possible)** - Regle implicite appliquee R3.2/R3.6/R3.7/R3.8 : Baserow = donnees user-visibles (les "databases" Notion-like que Ludo cree et regarde). Postgres DocAdenice = etat plateforme (auth, RBAC, contenu pages, notifs, comments, templates, backlinks). - 6 tables creees dans Postgres : `acadenice_backlink`, `acadenice_template`, `acadenice_notification`, `acadenice_notification_preferences`, `acadenice_page_comment`, `acadenice_row_comment`. Plus les 3 tables RBAC R2.1. - Justifications : FK CASCADE, latence <50ms requise pour panel backlinks, RBAC Docmost natif applique server-side, pas de pollution workspace Ludo, coherence yjs collab pour comments. - A reverifier ensemble si certaines de ces 6 tables devraient migrer vers Baserow (ex: templates editables comme une table user). Mon avis : non, mais ouvert au debat. ### 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.