ops: fix CI run — generate package-lock + bump testcontainers + doc 19 sync bidirec
- Add bridge/package-lock.json (npm install) — fix setup-node@v4 cache fail - Bump testcontainers 10.x -> 11.14.0 (fix high CVE undici) - Apply Biome formatter on src/index.ts + src/lib/config.ts - Update doc 19 Bridge API design : * Add 5th mission : sync bidirectionnel Docmost <-> Baserow * Add endpoints /docmost/* write + /sync/* orchestration * Add section MCP server (Phase 3+) with tools/resources/prompts * Add anti-loop strategy (X-Bridge-Origin + idempotence Redis)
This commit is contained in:
parent
668576cdc4
commit
d510bddc34
5 changed files with 5098 additions and 12 deletions
4931
bridge/package-lock.json
generated
Normal file
4931
bridge/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -35,7 +35,7 @@
|
|||
"@biomejs/biome": "^1.9.0",
|
||||
"@types/node": "^22.0.0",
|
||||
"@vitest/coverage-v8": "^2.1.0",
|
||||
"testcontainers": "^10.13.0",
|
||||
"testcontainers": "^11.14.0",
|
||||
"tsx": "^4.19.0",
|
||||
"typescript": "^5.6.0",
|
||||
"vitest": "^2.1.0"
|
||||
|
|
|
|||
|
|
@ -21,10 +21,7 @@ app.notFound((c) => c.json({ error: { code: 'NOT_FOUND', message: 'Route not fou
|
|||
|
||||
app.onError((err, c) => {
|
||||
logger.error({ err }, 'Unhandled error');
|
||||
return c.json(
|
||||
{ error: { code: 'INTERNAL', message: 'Internal server error' } },
|
||||
500,
|
||||
);
|
||||
return c.json({ error: { code: 'INTERNAL', message: 'Internal server error' } }, 500);
|
||||
});
|
||||
|
||||
serve({ fetch: app.fetch, port: config.port }, (info) => {
|
||||
|
|
|
|||
|
|
@ -33,7 +33,9 @@ export function loadConfig(): Config {
|
|||
});
|
||||
|
||||
if (!parsed.success) {
|
||||
const issues = parsed.error.issues.map((i) => ` - ${i.path.join('.')}: ${i.message}`).join('\n');
|
||||
const issues = parsed.error.issues
|
||||
.map((i) => ` - ${i.path.join('.')}: ${i.message}`)
|
||||
.join('\n');
|
||||
throw new Error(`Invalid configuration:\n${issues}`);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,14 +6,36 @@
|
|||
|
||||
## 1. Mission du bridge
|
||||
|
||||
Le bridge est notre **seul code custom**. Il a 4 missions :
|
||||
Le bridge est notre **seul code custom**. Il a **5 missions** :
|
||||
|
||||
1. **Exposer les rows Baserow comme objets typed** au reste de l'ecosysteme (typing strict, validation, cache)
|
||||
2. **Recevoir les webhooks Baserow** pour invalider caches et declencher actions (notifications, recalculs cross-zone)
|
||||
3. **Servir les Tiptap node-views custom** dans Docmost (mention `@formateur`, embed `[projet]`, etc.) avec donnees fraiches
|
||||
4. **Orchestrer les workflows metier** que ni Docmost ni Baserow ne savent faire seuls (validation RG, notifications croisees, capacite Personne unifiee)
|
||||
5. **Synchroniser bidirectionnel Docmost ↔ Baserow** — c'est la mission centrale. Le bridge ecrit dans **les deux systemes**, pas juste lecture.
|
||||
|
||||
Le bridge **ne stocke pas d'etat metier** (Phase 2). Source of truth = Baserow. Le bridge est stateless avec cache Redis.
|
||||
### 1.1 Bidirectionnalite — patterns de sync
|
||||
|
||||
| Pattern | Direction | Trigger | Effet |
|
||||
|---------|-----------|---------|-------|
|
||||
| **Auto-creation page Docmost** | Baserow → Docmost | Webhook `row.created` sur `projet` | Cree une page Docmost "Projet X" dans la collection `[AGENCE] Projets` avec template (statut, charge, taches embed) |
|
||||
| **Auto-creation collection** | Baserow → Docmost | Webhook `row.created` sur `formation` | Cree une collection Docmost `[CFA] Formation X` avec sub-pages par bloc |
|
||||
| **Maj metadata page** | Baserow → Docmost | Webhook `row.updated` sur `projet`/`formation` | Update title/icon/cover de la page Docmost liee |
|
||||
| **Auto-creation row Baserow** | Docmost → Baserow | Webhook page created (si template specifique) | Page "Compte-rendu reunion" cree → row dans table `comptes_rendus` |
|
||||
| **Mention enrichie** | Baserow → Docmost (lecture seule) | User tape `@personne:Pierre` dans editeur | Bridge resoud nom + capacite restante en live |
|
||||
| **Bouton action page** | Docmost → Baserow | User clique "Cloturer projet" sur la page Docmost | Bridge update `projet.statut = cloture` dans Baserow |
|
||||
|
||||
Le bridge **n'a pas d'etat metier propre** (Phase 2). Source of truth = Baserow pour la donnee structurée + Docmost pour le contenu rich. Le bridge est stateless avec cache Redis pour les jointures.
|
||||
|
||||
### 1.2 Strategie anti-boucle infinie
|
||||
|
||||
Risque : un webhook Baserow declenche un write Docmost qui declenche un webhook Docmost qui re-declenche un write Baserow → **boucle**.
|
||||
|
||||
Mitigations :
|
||||
- Header `X-Bridge-Origin: bridge` sur les writes du bridge (les webhooks ignorent les rows ayant ce flag)
|
||||
- Idempotence par event_id en Redis (TTL 24h)
|
||||
- Rate limit cote bridge sur les sync auto (max 1 sync identique / 5 min)
|
||||
- Logging exhaustif pour debug
|
||||
|
||||
## 2. Tech stack
|
||||
|
||||
|
|
@ -200,12 +222,30 @@ Tokens stockes en clair dans une table `api_tokens` Postgres (Phase 3+) ou en me
|
|||
| GET | `/api/v1/rapports/personne/:id?format=pdf` | `read:personnes` | PDF rapport personne (heures + attributions) |
|
||||
| GET | `/api/v1/rapports/projet/:id?format=pdf` | `read:projets` | PDF rapport projet |
|
||||
|
||||
### 6.7 Health & metrics
|
||||
### 6.7 Docmost — write endpoints (sync bidirectionnel)
|
||||
|
||||
| Method | Path | Scope | Description |
|
||||
|--------|------|-------|-------------|
|
||||
| POST | `/api/v1/docmost/pages` | `write:docmost` | Cree une page Docmost (avec template, parent, collection) |
|
||||
| PATCH | `/api/v1/docmost/pages/:id` | `write:docmost` | Update page (title, icon, cover, content partiel) |
|
||||
| POST | `/api/v1/docmost/collections` | `write:docmost` | Cree une collection Docmost |
|
||||
| POST | `/api/v1/docmost/share-links` | `write:docmost` | Genere un lien de partage (avec password/expiration) |
|
||||
|
||||
### 6.8 Sync bidirectionnel — endpoints orchestration
|
||||
|
||||
| Method | Path | Scope | Description |
|
||||
|--------|------|-------|-------------|
|
||||
| POST | `/api/v1/sync/projet/:id` | `admin:sync` | Force la sync d'un projet Baserow → page Docmost (utile pour reconciliation) |
|
||||
| POST | `/api/v1/sync/formation/:id` | `admin:sync` | Force la sync d'une formation → collection Docmost |
|
||||
| POST | `/api/v1/sync/all` | `admin:sync` | Sync complete batch (a executer manuellement, lent) |
|
||||
| GET | `/api/v1/sync/status` | `admin:sync` | Statut dernier sync, queue, erreurs |
|
||||
|
||||
### 6.9 Health & metrics
|
||||
|
||||
| Method | Path | Scope | Description |
|
||||
|--------|------|-------|-------------|
|
||||
| GET | `/api/health` | (none) | Healthcheck (200 si OK, 503 si degraded) |
|
||||
| GET | `/api/ready` | (none) | Readiness (Baserow + Redis joignables) |
|
||||
| GET | `/api/ready` | (none) | Readiness (Baserow + Docmost + Redis joignables) |
|
||||
| GET | `/api/metrics` | `admin:*` | Prometheus metrics format |
|
||||
|
||||
## 7. Webhooks Baserow
|
||||
|
|
@ -466,6 +506,105 @@ Exposees sur `/api/metrics` :
|
|||
- `cache_hits_total` / `cache_misses_total`
|
||||
- `webhook_events_processed_total{type,outcome}`
|
||||
|
||||
## 13bis. MCP server (Phase 3+)
|
||||
|
||||
> Idee retenue par Corentin le 2026-05-07 : exposer le bridge aussi comme **MCP server** pour permettre a Claude (et autres agents IA) d'interagir avec formation-hub.
|
||||
|
||||
### 13bis.1 Vision
|
||||
|
||||
Le bridge expose **deux protocoles** :
|
||||
- **REST** classique pour Docmost / Tiptap nodes / scripts (ce qui est specifie dans les sections 4-12)
|
||||
- **MCP** (Model Context Protocol) pour les agents IA — meme logique metier, transport different
|
||||
|
||||
Avec MCP, Claude peut :
|
||||
- Querier "donne-moi la capacite restante de Pierre Dupont"
|
||||
- Creer "attribue le module JS Fondamentaux a Pierre"
|
||||
- Generer rapports "fais-moi un rapport de la formation Dev Fullstack"
|
||||
- Detecter problemes "trouve les modules sans formateur attribue dans formation X"
|
||||
|
||||
Cas d'usage concret : un assistant IA pour l'admin pedagogique qui aide a equilibrer les charges, detecter les depassements, suggerer des reattributions.
|
||||
|
||||
### 13bis.2 Implementation
|
||||
|
||||
**Stack** :
|
||||
- `@modelcontextprotocol/sdk` (Node) — SDK officiel Anthropic
|
||||
- Le serveur MCP wrapping les services existants du bridge
|
||||
- Transport : stdio (pour Claude Code) OU HTTP/SSE (pour autres clients)
|
||||
|
||||
**Co-localisation** :
|
||||
- Meme codebase `bridge/`
|
||||
- Module `bridge/src/mcp/` qui re-utilise les services REST
|
||||
- Build separe : `npm run mcp:start` (binaire MCP standalone)
|
||||
- Ou meme proces : Hono REST + MCP SDK en parallele
|
||||
|
||||
### 13bis.3 Tools MCP exposes
|
||||
|
||||
| Tool | Description | Inputs |
|
||||
|------|-------------|--------|
|
||||
| `formation_hub_get_personne` | Detail personne avec capacite | `personne_id` ou `email` |
|
||||
| `formation_hub_search_personnes` | Liste filtree | `roles`, `statut`, `capacite_min` |
|
||||
| `formation_hub_get_formation` | Detail formation + blocs + modules | `formation_id` |
|
||||
| `formation_hub_get_projet` | Detail projet + taches + interventions | `projet_id` |
|
||||
| `formation_hub_create_attribution` | Cree attribution avec validation RG | `module_id`, `personne_id`, `heures`, `date_debut` |
|
||||
| `formation_hub_saisir_intervention` | Saisie intervention dev | `tache_id`, `personne_id`, `heures`, `date` |
|
||||
| `formation_hub_get_capacite_unifiee` | Vue 360 capacite Personne (formation + agence) | `personne_id` |
|
||||
| `formation_hub_search_modules_a_attribuer` | Modules en attente d'attribution | `formation_id?` |
|
||||
| `formation_hub_generer_rapport` | Genere rapport PDF | `type` (formation/personne/projet), `id`, `format` |
|
||||
| `formation_hub_simulate_attribution` | Dry-run attribution avec impact rollups (sans ecrire) | comme `create_attribution` |
|
||||
|
||||
### 13bis.4 Resources MCP
|
||||
|
||||
Resources MCP = donnees exposees en lecture (vs tools qui sont des actions) :
|
||||
- `formation-hub://personnes` — liste des personnes
|
||||
- `formation-hub://personnes/{id}` — detail personne
|
||||
- `formation-hub://formations` — liste formations
|
||||
- `formation-hub://projets/active` — projets en cours
|
||||
- `formation-hub://capacity-overview` — synthese capacite globale equipe
|
||||
|
||||
### 13bis.5 Prompts MCP
|
||||
|
||||
Prompts MCP = templates Claude prets a l'emploi :
|
||||
- `equilibrer-charges` : Claude analyse les attributions et propose des rebalancing
|
||||
- `detecter-depassements` : Claude scan les capacites et signale les problemes
|
||||
- `planifier-formation` : Claude aide a decomposer une formation en blocs/modules
|
||||
- `synthese-projet` : Claude genere un rapport status pour un projet client
|
||||
|
||||
### 13bis.6 Auth MCP
|
||||
|
||||
- API token bridge (`brg_*` tokens, scope `admin:mcp`)
|
||||
- Token configure dans le client MCP (`.claude/settings.json` cote Claude Code)
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"formation-hub": {
|
||||
"command": "node",
|
||||
"args": ["bridge/dist/mcp-server.js"],
|
||||
"env": {
|
||||
"BRIDGE_API_TOKEN": "brg_xxxx",
|
||||
"BRIDGE_URL": "https://bridge.acadenice.fr"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 13bis.7 Roadmap MCP
|
||||
|
||||
| Phase | Effort | Livrable |
|
||||
|-------|--------|----------|
|
||||
| Phase 3.0 (apres bridge REST stable) | 1-2 semaines | MCP server avec 5 tools cles (get_personne, get_formation, capacite_unifiee, search_modules_a_attribuer, simulate_attribution) |
|
||||
| Phase 3.1 | 1 semaine | Tools writes (create_attribution, saisir_intervention) |
|
||||
| Phase 3.2 | 1 semaine | Resources + prompts |
|
||||
| Phase 4 | continu | Integration dans workflows admin (Yan/Corentin utilisent Claude pour piloter formation-hub) |
|
||||
|
||||
### 13bis.8 Decisions a prendre Phase 3
|
||||
|
||||
- [ ] Transport MCP : stdio only (Claude Code local) ou HTTP/SSE (multi-client web) ?
|
||||
- [ ] Co-localisation MCP + REST dans meme proces ou processus separes ?
|
||||
- [ ] MCP expose sur quel hostname ? (probablement pas accessible depuis internet, juste localhost ou VPN interne)
|
||||
- [ ] Authoring tools : Claude peut-il creer/modifier des entites ou seulement lire ? Decision selon trust ELO Claude vs human-in-the-loop pattern.
|
||||
|
||||
## 14. Tests
|
||||
|
||||
Cf doc 16 plan-de-tests :
|
||||
|
|
@ -507,12 +646,22 @@ Cf doc 16 plan-de-tests :
|
|||
- [ ] Integration Docmost (fork ou plugin)
|
||||
- [ ] E2E playwright
|
||||
|
||||
### Phase 2.4 — Pages full + rapports (semaine 11-12)
|
||||
### Phase 2.4 — Pages full + rapports + sync bidirec (semaine 11-12)
|
||||
|
||||
- [ ] Routes /personne/:id, /projet/:id en page Docmost-style
|
||||
- [ ] Endpoint /rapports/* PDF generation
|
||||
- [ ] Endpoints `/docmost/pages` write (auto-creation depuis evenements Baserow)
|
||||
- [ ] Endpoints `/sync/*` orchestration manuelle
|
||||
- [ ] Strategie anti-boucle infinie (header `X-Bridge-Origin`, idempotence)
|
||||
- [ ] Stabilisation, fix bugs, doc utilisateur
|
||||
|
||||
### Phase 3.0 — MCP server (apres bridge REST stable)
|
||||
|
||||
- [ ] Setup `@modelcontextprotocol/sdk` Node
|
||||
- [ ] 5 tools cles read-only
|
||||
- [ ] Auth via API token (scope `admin:mcp`)
|
||||
- [ ] Documentation client (Claude Code config)
|
||||
|
||||
## 16. Decisions a prendre
|
||||
|
||||
- [ ] **Source of truth tokens** : .env (simple) vs Postgres dedie (rotation a chaud) ? Mon vote : .env Phase 2, Postgres Phase 3
|
||||
|
|
@ -522,7 +671,14 @@ Cf doc 16 plan-de-tests :
|
|||
- [ ] **GraphQL au lieu de REST ?** Pas pertinent pour notre scope (peu de endpoints, peu de variation queries). REST est plus simple.
|
||||
- [ ] **Multi-tenant** ? Pour l'instant non — Acadenice mono-instance. Si rachat / scaling : ajouter `tenant_id` partout. Pas avant Phase 4.
|
||||
|
||||
## 17. Glossaire
|
||||
## 17. Decisions techniques notables (deja prises)
|
||||
|
||||
- **Bidirectionnalite** : confirme par Corentin le 2026-05-07. Bridge ecrit dans Docmost ET Baserow (5 missions au lieu de 4).
|
||||
- **MCP server** : retenu pour Phase 3+. Expose les memes capacites bridge via le protocol MCP pour usage par Claude / autres agents IA.
|
||||
- **Pas d'etat metier dans le bridge** : reste stateless en Phase 2. Source of truth Baserow + Docmost.
|
||||
- **Anti-boucle webhooks** : header `X-Bridge-Origin` + idempotence Redis.
|
||||
|
||||
## 18. Glossaire
|
||||
|
||||
| Terme | Definition |
|
||||
|-------|------------|
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue