Service account pattern resolves 401 PERMISSION_DENIED on Baserow metadata endpoints (/api/database/views/table/:id/, /api/database/tables/:id/) which reject DB tokens. A dedicated Baserow user account logs in via token-auth, JWT cached in memory with mutex-protected refresh before expiry. Fallback graceful: if BASEROW_USER_EMAIL/PASSWORD absent, CRUD rows still work, metadata endpoints return 500 BASEROW_USER_AUTH_NOT_CONFIGURED. 417 tests pass (was 392, +25). 0 TS errors. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
58 KiB
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.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) :
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.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
/graphinteractif (zoom, pan, drag, click, side panel, search) — LIVREaac0149(58 tests, Patch 013) - Depend de R3.2 (backlinks fournit la data)
- R3.5 ENTIEREMENT TERMINE
- R3.5.1 backend :
- R3.6 templates de pages — LIVRE
614533f(65 tests, Patch 014). Tableacadenice_template, 5 built-ins seedes, gallery /settings/templates, picker modal sidebar + slash /template, 26 permissions. - R3.7 mentions
@user+ notifs in-app — LIVRE7d076aa(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:useajoutee 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_DATAmessage 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 :
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.tsxapps/client/src/features/acadenice/database-view/renderers/timeline-renderer.module.cssapps/client/src/features/acadenice/database-view/hooks/use-timeline-config.tsapps/client/src/features/acadenice/database-view/__tests__/timeline-renderer.test.tsxbridge/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 responseapps/client/src/features/acadenice/database-view/extension/database-view-component.tsx— dispatch timelineapps/client/src/features/acadenice/database-view/slash-command/insert-database-modal.tsx— step 3 mapping timelineapps/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
PersistenceExtensionetendue : prefixsync-block-*route versloadSyncBlockDoc/storeSyncBlockDoc(separate de la persistence pages) - Cycle detection BFS depth 5 via
extractMasterIdsFromProseMirror+assertNoCycledansSyncBlocksService(ConflictException('CYCLIC_SYNC_BLOCK')) - SSE broadcast :
SyncBlockBroadcastServicewraps EventEmitter2, hook clientuseSyncBlockRealtimereconnecte avec backoff expo 1s->30s - Collab client :
HocuspocusProviderpointe sur docsync-block-{masterId}, overlay Mantine Modal
Fichiers crees (server) :
apps/server/src/database/migrations/20260509T100000-create-acadenice-sync-block.tsapps/server/src/core/acadenice/sync-blocks/dto/sync-block.dto.tsapps/server/src/core/acadenice/sync-blocks/repos/sync-block.repo.tsapps/server/src/core/acadenice/sync-blocks/services/sync-blocks.service.tsapps/server/src/core/acadenice/sync-blocks/services/sync-block-broadcast.service.tsapps/server/src/core/acadenice/sync-blocks/controllers/sync-blocks.controller.tsapps/server/src/core/acadenice/sync-blocks/sync-blocks.module.tsapps/server/src/core/acadenice/sync-blocks/spec/(4 fichiers spec)
Fichiers crees (client) :
apps/client/src/features/acadenice/sync-blocks/extension/sync-block-extension.tsapps/client/src/features/acadenice/sync-blocks/components/sync-block-node-view.tsxapps/client/src/features/acadenice/sync-blocks/hooks/use-sync-block-realtime.tsapps/client/src/features/acadenice/sync-blocks/services/sync-blocks-client.tsapps/client/src/features/acadenice/sync-blocks/slash-command/insert-sync-block.tsapps/client/src/features/acadenice/sync-blocks/__tests__/(3 fichiers test)
Fichiers modifies :
apps/server/src/collaboration/extensions/persistence.extension.ts— routing sync-block-* docsapps/server/src/collaboration/collaboration.module.ts— providers SyncBlockRepo + SyncBlockBroadcastServiceapps/server/src/core/acadenice/rbac/permissions-catalog.ts— 3 perms sync_blocksapps/server/src/core/acadenice/rbac/services/seed.service.ts— assignation rolesapps/server/src/core/core.module.ts— import AcadeniceSyncBlocksModuleapps/client/src/features/editor/extensions/extensions.ts— SyncBlockExtension dans mainExtensionsapps/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), factorycreateBaserowJwtManagerbridge/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 stubbridge/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: champbaserowJwt: BaserowJwtManager, init au boot, log enabled/disabledbridge/src/adapters/baserow-client.ts: 2 nouvelles methodeslistViewsWithJwt+getTableWithJwt(headerJWT <token>)bridge/src/repos/baserow-views-repo.ts:jwtManagerinjectable,listViewsResolved()route via JWT si dispo, sinon DB token, 503 si unconfiguredbridge/tests/helpers/test-app.ts:baserowJwtinjectable dans test container
Vars d'env a ajouter en prod :
BASEROW_USER_EMAIL=bridge-svc@interne.local
BASEROW_USER_PASSWORD=<generated>
# optionnel
BASEROW_JWT_REFRESH_MARGIN=60
Deploiement :
- Creer user Baserow via
/admin/users/(Is staff: non, Member sur le workspace) - Ajouter les 2 vars en prod (Docker secrets /
.env) - Redemarrer le bridge — log "Baserow user JWT manager enabled" confirme
- Verifier :
curl -H "Authorization: Bearer brg_<token>" 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.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.