chore(deploy): enable bridge service and ignore byan-output
Some checks are pending
CI / Lint bridge (Biome) (push) Waiting to run
CI / Type-check bridge (push) Blocked by required conditions
CI / Tests unit bridge (push) Blocked by required conditions
CI / Tests integration bridge (push) Blocked by required conditions
CI / Security scan (push) Waiting to run
CI / Docker build + healthcheck (push) Blocked by required conditions
E2E Playwright / Playwright e2e (chromium) (push) Waiting to run

- Uncomment bridge service in compose.yml so stack runs end-to-end on prod
- Add _byan-output/ to .gitignore (BYAN session notes, may contain credentials)
- Untrack previously committed _byan-output/ files
- Include e2e Stagehand config and smoke env template
This commit is contained in:
Corentin JOGUET 2026-05-10 15:16:38 +02:00
parent 445dda260a
commit 90a7de3388
13 changed files with 3469 additions and 1140 deletions

3
.gitignore vendored
View file

@ -55,3 +55,6 @@ venv/
# Docmost fork — historique git separe (depth=1 clone), futur submodule sur fork Acadenice
docmost/
# BYAN session outputs — notes de travail internes, ne jamais commit (contiennent parfois credentials)
_byan-output/

View file

@ -1,858 +0,0 @@
# 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 <token>`)
- `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=<generated>
# 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_<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.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=<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 + 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:<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 `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 <jwt>` OU cookie `authToken=<jwt>` (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:<sha256(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.

View file

@ -1,29 +0,0 @@
{
"scope_mode": "full",
"features": [
{"id": "F-01", "title": "Wiki Docmost avec spaces multi-tenant", "priority": "MUST", "justification": "Coeur metier — centralisation doc"},
{"id": "F-02", "title": "Diagrammes natifs Mermaid + Drawio + Excalidraw", "priority": "MUST", "justification": "Inclus Docmost v0.3+, zero dev"},
{"id": "F-03", "title": "Permissions hierarchiques (workspace/space/page)", "priority": "MUST", "justification": "RGPD + workflow team"},
{"id": "F-04", "title": "Share links externes (clients guests)", "priority": "MUST", "justification": "Acces partenaires/financeurs"},
{"id": "F-05", "title": "9 tables Baserow (PERSONNE pivot + CFA + Agence)", "priority": "MUST", "justification": "Modele de donnees scope B approved"},
{"id": "F-06", "title": "Rollups + formulas heures restantes", "priority": "MUST", "justification": "Calcul auto capacite formateurs/devs"},
{"id": "F-07", "title": "Vues kanban/calendar/timeline par DB", "priority": "MUST", "justification": "UX metier admin"},
{"id": "F-08", "title": "Spaces personnels etudiants Docmost", "priority": "MUST", "justification": "Promesse Vision Acadenice"},
{"id": "F-09", "title": "Forms publics saisie heures (formateurs/devs)", "priority": "SHOULD", "justification": "UX mobile-friendly + permissions simples"},
{"id": "F-10", "title": "Bridge service Tiptap node-views custom", "priority": "SHOULD", "justification": "UX unifie Phase 2"},
{"id": "F-11", "title": "Sync bidirectionnel Docmost ↔ Baserow", "priority": "SHOULD", "justification": "Auto-creation pages depuis projets, etc."},
{"id": "F-12", "title": "MCP server pour Claude/agents IA", "priority": "COULD", "justification": "Productivity boost pour admin Yan/Corentin"},
{"id": "F-13", "title": "Bidirec backlinks Docmost (custom)", "priority": "COULD", "justification": "Nice-to-have, le bridge couvre 80% du besoin via DB relations"},
{"id": "F-14", "title": "Dual-mode editor (WYSIWYG + raw markdown)", "priority": "COULD", "justification": "Power-users only"},
{"id": "F-15", "title": "Rapports PDF (formation/personne/projet)", "priority": "COULD", "justification": "Export pour comptabilite, Phase 3"}
],
"wont": [
{"id": "W-01", "title": "Modeliser les etudiants en table Baserow", "reason": "Decision Corentin 2026-05-07 : etudiants restent users Docmost libres, pas de modelisation structuree (inscriptions/notes). Si besoin Phase 4."},
{"id": "W-02", "title": "Generer factures clients automatiquement", "reason": "Hors scope outil de connaissance. Comptabilite via outil dedie."},
{"id": "W-03", "title": "ATS / recrutement", "reason": "Pas le metier, hors scope."},
{"id": "W-04", "title": "Application mobile native", "reason": "Responsive web suffit. Mobile-native couterait 6 mois. Ockham."},
{"id": "W-05", "title": "Multi-tenant (plusieurs centres de formation sur instance)", "reason": "Acadenice mono-instance pour l'instant. A reevaluer si scale."}
],
"validated_at": "2026-05-07",
"validated_by": "Corentin JOGUET"
}

View file

@ -1,15 +0,0 @@
{
"current_iteration": 1,
"completed": [],
"pending": [1, 2, 3, 4, 5, 6, 7],
"next_iteration": 1,
"stack_local_status": {
"docmost": "up + healthy on http://localhost:3000",
"baserow": "up + healthy on http://localhost:8080",
"docmost-db": "up + healthy",
"docmost-redis": "up",
"verified_at": "2026-05-07T14:48:00Z"
},
"ready_for_iteration_1": true,
"iteration_1_first_step": "Creer compte admin Baserow via http://localhost:8080 (1ere page invite a creer un compte). Apres : creer database 'formation-hub', puis table 'personne'."
}

View file

@ -1,93 +0,0 @@
{
"scope": "Phase 1 vanilla setup metier — focalise BUILD iteration 1-3",
"stories": [
{
"id": "S-01",
"feature_id": "F-01",
"connextra": "En tant qu'Admin Acadenice, je veux creer un workspace Docmost et 3 spaces (CFA, Agence, Interne), afin de centraliser la doc avec une structure miroir des collections Outline existantes.",
"ac": [
{"given": "compte admin Docmost cree", "when": "je cree workspace 'Acadenice formation-hub'", "then": "workspace existe avec moi en owner"},
{"given": "workspace cree", "when": "je cree spaces CFA/Agence/Interne avec permissions par defaut 'workspace members'", "then": "3 spaces visibles sidebar"}
]
},
{
"id": "S-02",
"feature_id": "F-05",
"connextra": "En tant qu'Admin, je veux creer la table PERSONNE dans Baserow avec tous ses champs et formulas selon doc 15 MPD section 2, afin d'avoir le pivot multi-roles operationnel.",
"ac": [
{"given": "database 'formation-hub' creee", "when": "je cree la table PERSONNE avec 16 fields (incluant capacity_annuelle, split_pcts, roles multi-select, formulas heures_restantes)", "then": "la table est listee dans la database et les fields ont les bons types"},
{"given": "table PERSONNE creee", "when": "je cree une row test (Yan, role formateur+developpeur, capacity 1500)", "then": "les formulas heures_restantes affichent 750/750/1500"}
]
},
{
"id": "S-03",
"feature_id": "F-05",
"connextra": "En tant qu'Admin, je veux creer les 4 tables CFA (FORMATION → BLOC → MODULE → ATTRIBUTION) avec leurs liens FK et rollups, afin que le suivi des heures formation soit operationnel.",
"ac": [
{"given": "table PERSONNE existe", "when": "je cree FORMATION, BLOC, MODULE, ATTRIBUTION dans l'ordre avec liens vers PERSONNE pour ATTRIBUTION", "then": "les 4 tables existent avec les liens visibles bidirectionnellement"},
{"given": "tables CFA crees", "when": "je cree formation test avec 1 bloc + 1 module + 1 attribution a Yan", "then": "les rollups formation_heures_attribuees, bloc_heures_attribuees, module_heures_attribuees sont calcules"}
]
},
{
"id": "S-04",
"feature_id": "F-05",
"connextra": "En tant qu'Admin, je veux creer les 4 tables Agence (CLIENT → PROJET → TACHE → INTERVENTION) avec liens FK et rollups, afin de tracer les projets clients.",
"ac": [
{"given": "table PERSONNE existe", "when": "je cree CLIENT, PROJET, TACHE, INTERVENTION avec liens", "then": "tables existent + lien optionnel PROJET ↔ FORMATION pour projet pedagogique"},
{"given": "tables creees", "when": "j'ajoute client Centralis Europe + projet test + tache + intervention", "then": "les rollups projet_heures_realisees + tache_heures_realisees calculent"}
]
},
{
"id": "S-05",
"feature_id": "F-07",
"connextra": "En tant qu'Admin, je veux creer les vues recommandees doc 15 par table (table, kanban, calendar) afin d'avoir l'UX metier prete pour onboarding.",
"ac": [
{"given": "tables existent", "when": "je cree vue 'A attribuer' kanban sur MODULE group by module_statut", "then": "vue affiche kanban fonctionnel"},
{"given": "vues creees", "when": "je verifie les vues principales par table (Tous, Kanban, Calendar selon doc 15)", "then": "minimum 9 vues fonctionnelles (~1 par table)"}
]
},
{
"id": "S-06",
"feature_id": "F-09",
"connextra": "En tant que Formateur, je veux saisir mes heures realisees via un form public Baserow sans compte, afin de logger rapidement depuis mobile.",
"ac": [
{"given": "table ATTRIBUTION existe", "when": "Admin cree form view publique 'Saisir heures realisees' sur ATTRIBUTION (champs limites)", "then": "formateur peut acceder par lien et soumettre"},
{"given": "form public actif", "when": "formateur saisit attribution + heures + date", "then": "row creee, rollups recalcules"}
]
},
{
"id": "S-07",
"feature_id": "F-04",
"connextra": "En tant qu'Admin, je veux generer un share link Docmost pour une page support de formation, afin qu'un client puisse consulter sans creer de compte.",
"ac": [
{"given": "page Docmost existante", "when": "je clique 'Share' et configure expiration 7j + password", "then": "lien genere fonctionne en navigation privee sans login"}
]
},
{
"id": "S-08",
"feature_id": "F-08",
"connextra": "En tant qu'Admin, je veux pouvoir creer rapidement un space personnel pour un nouvel etudiant avec template, afin de l'onboarder en moins de 2 min.",
"ac": [
{"given": "Docmost workspace 'Acadenice formation-hub'", "when": "je cree space 'Etudiant - Marie Dupont' visibility prive (Marie + admins)", "then": "space cree, Marie peut creer/editer ses pages, autres etudiants ne le voient pas"}
]
},
{
"id": "S-09",
"feature_id": "F-05",
"connextra": "En tant qu'Admin, je veux generer un API token Baserow scope read+write pour le bridge service Phase 2, afin que le code custom puisse interroger les tables.",
"ac": [
{"given": "database formation-hub ok", "when": "je cree API token via Settings → API tokens", "then": "token genere fonctionne sur curl GET /api/database/rows/table/X/"}
]
},
{
"id": "S-10",
"feature_id": "F-01",
"connextra": "En tant qu'Admin, je veux backup quotidien automatique de Postgres docmost + data Baserow + uploads, afin d'avoir RPO 24h conforme CDC.",
"ac": [
{"given": "stack up", "when": "le cron quotidien 03:00 execute scripts/backup.sh", "then": "fichiers .sql.gz et .tar.gz crees dans backups/local/, retention 30 jours"}
]
}
],
"validated_at": null,
"next_step": "PLAN d'iterations BUILD"
}

View file

@ -1,55 +0,0 @@
{
"assignments": [
{
"iteration_idx": 1,
"specialist": "Claude Code (Sonnet 4.6) + Corentin",
"model_tier": "haiku",
"rationale": "Setup Baserow tables = action UI repetitive. Claude guide etape par etape, Corentin clique. Pas besoin de gros raisonnement.",
"automation_possible": "Partiel — l'API Baserow permet de creer tables/fields programmatiquement, mais l'UI Baserow est plus rapide pour le 1er setup"
},
{
"iteration_idx": 2,
"specialist": "Claude Code + Corentin",
"model_tier": "sonnet",
"rationale": "Formulas Baserow ont une syntaxe specifique (field('x'), lookup, count, sum). Sonnet pour bien syntaxer les formulas du doc 15.",
"automation_possible": "Partiel"
},
{
"iteration_idx": 3,
"specialist": "Corentin solo",
"model_tier": "haiku",
"rationale": "Setup Docmost UI = action de config standard. Pas besoin Claude.",
"automation_possible": "Faible — Docmost API est limitee pour creation workspace/spaces"
},
{
"iteration_idx": 4,
"specialist": "Corentin + (eventuellement Claude pour script create-space-etudiant)",
"model_tier": "haiku",
"rationale": "Pattern repetitif → automatisable via script bash + Docmost API",
"automation_possible": "Oui via script"
},
{
"iteration_idx": 5,
"specialist": "Corentin (DevOps son metier)",
"model_tier": "haiku",
"rationale": "API token + cron + smoke = du DevOps pur. Corentin maitrise.",
"automation_possible": "100% script"
},
{
"iteration_idx": 6,
"specialist": "Corentin + Yan + Sophie",
"model_tier": "n/a (humain)",
"rationale": "Migration data necessite knowledge metier (qui est qui dans les RH, quels clients, etc.). Pas Claude — humains internes.",
"automation_possible": "Partiel : Claude peut transformer CSV → format Baserow API"
},
{
"iteration_idx": 7,
"specialist": "Equipe Acadenice (Yan, Ludo, Corentin) + 5-10 testeurs",
"model_tier": "n/a (humain)",
"rationale": "Test reel necessite humains. Claude peut compiler les retours en backlog priorise apres.",
"automation_possible": "Non"
}
],
"halt": null,
"validated_at": null
}

View file

@ -1,10 +0,0 @@
{
"name_app": "formation-hub",
"one_liner": "Notion-like self-host pour Acadenice (CFA + Agence dev) avec suivi heures formateurs/devs unifie",
"who": "Equipe Acadenice (~20 employes : direction Ludo, resp tech Yan, AdminSys/DevOps Corentin, formateurs, devs) + ~70 etudiants (spaces personnels libres) + clients guests (acces lien partage). Cible totale : 90-100 users, ~30 simultanes peak.",
"what": "Plateforme self-host composite : (1) Wiki collaboratif (Docmost AGPL) avec diagrammes natifs Mermaid/Drawio/Excalidraw + share links + spaces multi-tenant. (2) Bases de donnees structurees (Baserow MIT) pour le suivi heures formation/agence avec entite PERSONNE pivot multi-roles. (3) Bridge custom Node TS (Phase 2) qui synchronise Docmost et Baserow bidirectionnel et expose des Tiptap nodes custom pour UX unifie. (4) MCP server (Phase 3) pour interaction Claude/agents IA.",
"why": "Centraliser la doc + suivre les heures formation/agence dans un outil unifie self-host **illimite users**. Alternatives ecartees : Notion paye au seat, AFFiNE limite a 10 seats free, AppFlowy limite a 1 user free, Outline pas de bidirec backlinks. Acadenice a une double casquette CFA + Agence dev (formateurs = devs sur projets clients) = capacite annuelle splittee entre les deux activites. Aucun outil existant ne modelise ca correctement.",
"context": "Phase 0 conception complete (19 docs Merise Agile + UML + GitOps). Repo : github.com/AcadeNice/wiki + git.acadenice.com/AcadeNice/Wiki (selfhost source of truth). Stack Docker compose locale up et healthy au 2026-05-07.",
"validated_at": "2026-05-07",
"validated_by": "Corentin JOGUET"
}

View file

@ -1,65 +0,0 @@
{
"iterations": [
{
"idx": 1,
"name": "I1 — Setup Baserow vanilla (tables + liens)",
"stories": ["S-02", "S-03", "S-04"],
"expected_loops": 2,
"definition_of_done": "9 tables creees dans Baserow database 'formation-hub' avec tous les liens FK fonctionnels (testes manuellement avec rows-temoin). Pas encore de formulas/rollups complexes — juste structure.",
"deliverable": "Schema Baserow exporte JSON dans baserow/schemas/*.json + screenshots de chaque table"
},
{
"idx": 2,
"name": "I2 — Formulas, rollups, vues",
"stories": ["S-02 (formulas part)", "S-05"],
"expected_loops": 2,
"definition_of_done": "Toutes les formulas du doc 15 sont actives + 9+ vues recommandees (kanban, calendar, table) crees. Rows test confirment les calculs.",
"deliverable": "Vues exportees + screenshots dashboards"
},
{
"idx": 3,
"name": "I3 — Setup Docmost workspace + permissions + share",
"stories": ["S-01", "S-07"],
"expected_loops": 2,
"definition_of_done": "Workspace + 3 spaces + permissions par defaut + 1 page test partagee par lien public expire 7j",
"deliverable": "Captures workspace + URL share test"
},
{
"idx": 4,
"name": "I4 — Spaces etudiants + form public saisie heures",
"stories": ["S-08", "S-06"],
"expected_loops": 2,
"definition_of_done": "Pattern create-space-etudiant valide (script ou checklist 2-min) + form public Baserow ATTRIBUTION accessible mobile",
"deliverable": "Doc onboarding etudiant + URL form public"
},
{
"idx": 5,
"name": "I5 — API token + backup automatise + smoke test E2E",
"stories": ["S-09", "S-10"],
"expected_loops": 1,
"definition_of_done": "Token Baserow fonctionnel + cron backup setup + scripts/healthcheck.sh + scripts/smoke-test.sh passent",
"deliverable": "Token stocke (vault/.env), 1ere execution backup reussie, logs"
},
{
"idx": 6,
"name": "I6 — Migration data initiale (formations + clients existants)",
"stories": ["data migration"],
"expected_loops": 3,
"definition_of_done": "Donnees reelles Acadenice importees depuis sources actuelles (Excel/Trello/autre) dans Baserow, integrite verifiee (rollups coherents avec realite metier)",
"deliverable": "Rapport migration : nb rows attendus vs imported, cas speciaux"
},
{
"idx": 7,
"name": "I7 — Onboarding 5-10 power users + retours UX",
"stories": ["onboarding"],
"expected_loops": 2,
"definition_of_done": "5-10 personnes Acadenice (Yan, Ludo, Sophie, 2-3 formateurs, 2 devs) ont utilise pendant 1 semaine + retours collectes",
"deliverable": "Backlog UX issues priorise"
}
],
"phases_apres_plan": [
"Iterations Phase 2 (bridge custom) seront planifiees apres I7 selon douleurs reelles identifiees",
"MPD Baserow concret est dans doc 15-baserow-mpd.md"
],
"validated_at": null
}

View file

@ -54,20 +54,20 @@ services:
volumes:
- baserow-data:/baserow/data
# bridge:
# build: ./bridge
# restart: unless-stopped
# depends_on:
# - baserow
# - docmost-redis
# environment:
# BASEROW_API_URL: http://baserow:80/api
# BASEROW_API_TOKEN: ${BASEROW_API_TOKEN}
# DOCMOST_API_URL: http://docmost:3000/api
# DOCMOST_API_TOKEN: ${DOCMOST_API_TOKEN}
# REDIS_URL: redis://docmost-redis:6379
# ports:
# - "4000:4000"
bridge:
build: ./bridge
restart: unless-stopped
depends_on:
- baserow
- docmost-redis
environment:
BASEROW_API_URL: http://baserow:80/api
BASEROW_API_TOKEN: ${BASEROW_API_TOKEN}
DOCMOST_API_URL: http://docmost:3000/api
DOCMOST_API_TOKEN: ${DOCMOST_API_TOKEN}
REDIS_URL: redis://docmost-redis:6379
ports:
- "4000:4000"
volumes:
docmost-db:

27
e2e/.env.smoke.example Normal file
View file

@ -0,0 +1,27 @@
# AcadeDoc smoke suite — environment template.
# Copy to e2e/.env.smoke (gitignored) and fill in the secrets.
# Targets — playwright.smoke.config.ts and the Stagehand suite both read these.
PLAYWRIGHT_BASE_URL=http://localhost:5173
PLAYWRIGHT_SERVER_URL=http://localhost:3001
PLAYWRIGHT_BRIDGE_URL=http://localhost:4000
# Real AcadeDoc user used to login through the UI (NOT a synthetic e2e fixture).
PLAYWRIGHT_USER_EMAIL=corentin@acadenice.fr
PLAYWRIGHT_USER_PASSWORD=changeme
# --- Stagehand (R4.8) ---
# Required to run `pnpm run smoke:stagehand`. Get a key at console.anthropic.com.
# When empty, the Stagehand suite refuses to start and the Playwright-pure
# smoke suite (R4.7) still works without it.
ANTHROPIC_API_KEY=
# Model used as the action planner. Haiku is the cheap-and-fast default.
# Other valid values: anthropic/claude-sonnet-4-6, anthropic/claude-opus-4-6.
STAGEHAND_MODEL=anthropic/claude-haiku-4-5-20251001
# "true" to launch a headed Chromium (debug). Default headless.
STAGEHAND_HEADED=false
# 0 = silent, 1 = info, 2 = debug. Default 1.
STAGEHAND_VERBOSE=1

View file

@ -14,10 +14,13 @@
"smoke:full": "playwright test --config=playwright.smoke.config.ts; tsx scripts/generate-smoke-report.ts"
},
"devDependencies": {
"@browserbasehq/stagehand": "^3.3.0",
"@playwright/test": "^1.44.0",
"dotenv": "^16.4.5",
"playwright-core": "^1.59.1",
"tsx": "^4.21.0",
"typescript": "^5.4.5"
"typescript": "^5.4.5",
"zod": "^4.4.3"
},
"engines": {
"node": ">=22"

3351
e2e/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load diff

70
e2e/stagehand.config.ts Normal file
View file

@ -0,0 +1,70 @@
/**
* Stagehand configuration for the AcadeDoc semantic smoke suite R4.8.
*
* Stagehand (Browserbase, MIT) wraps Playwright with an LLM-driven action
* planner. Instead of hardcoded selectors (Playwright pure), we describe the
* intent in natural language and the LLM resolves the right element at runtime.
* That makes the suite resilient to UI label/markup churn the same test still
* works after a Mantine version bump or a French/English label rename.
*
* This config is consumed both by tests/acadenice-smoke-stagehand.spec.ts and
* by any direct script that imports `buildStagehand()` to drive a session.
*
* Env vars (read from e2e/.env.smoke):
* ANTHROPIC_API_KEY required, the LLM that powers act/observe/extract
* STAGEHAND_MODEL optional, defaults to anthropic/claude-haiku-4-5-20251001
* (haiku = fast + cheap, sufficient for UI navigation)
* STAGEHAND_HEADED "true" to run with a visible browser, default false
* STAGEHAND_VERBOSE "0" | "1" | "2", default "1"
*/
import { Stagehand } from "@browserbasehq/stagehand";
import * as dotenv from "dotenv";
import * as path from "path";
dotenv.config({ path: path.resolve(__dirname, ".env.smoke") });
export interface StagehandConfig {
modelName: string;
apiKey: string;
headless: boolean;
verbose: 0 | 1 | 2;
}
export function readConfig(): StagehandConfig {
const apiKey = process.env.ANTHROPIC_API_KEY ?? "";
const modelName =
process.env.STAGEHAND_MODEL ?? "anthropic/claude-haiku-4-5-20251001";
const headed = (process.env.STAGEHAND_HEADED ?? "").toLowerCase() === "true";
const verboseRaw = process.env.STAGEHAND_VERBOSE ?? "1";
const verbose = (["0", "1", "2"].includes(verboseRaw)
? Number(verboseRaw)
: 1) as 0 | 1 | 2;
return { modelName, apiKey, headless: !headed, verbose };
}
/**
* Build a Stagehand instance pointed at a local Chromium with Anthropic Claude
* as the planning model. Caller is responsible for `await stagehand.init()` and
* `await stagehand.close()`.
*/
export function buildStagehand(): Stagehand {
const cfg = readConfig();
if (!cfg.apiKey) {
throw new Error(
"ANTHROPIC_API_KEY missing in e2e/.env.smoke — cannot build Stagehand instance",
);
}
return new Stagehand({
env: "LOCAL",
verbose: cfg.verbose,
model: {
modelName: cfg.modelName,
apiKey: cfg.apiKey,
},
localBrowserLaunchOptions: {
headless: cfg.headless,
viewport: { width: 1440, height: 900 },
},
});
}