v3 : socle Rust (moteur pur, accès trustinWallet, serveur) ; maquette Nuxt rangée dans maquette/

- crates/ld-engine : formule inertielle (parité avec la maquette, garde C),
  coquille / changement de fond, assistant d'édition à règles, composition
  d'un document sous vote permanent (adoptions, aperçus, rivales).
- crates/trustin-access : qui voit, qui ouvre, qui gère les accès.
- crates/ld-server : API axum + SQLite, connexion Ğ1 (client OAuth2 de
  connect.monnaie-libre.fr), invitations, entrée en vigueur automatique,
  signalements de contradiction, traductions éditoriales, publication git,
  taille de la toile lue sur la chaîne.
- seeds/acte-engagement-certification.json : 33 articles, fr + en + es ;
  en vigueur : préambule, E1, E2 (seed a minima).
- maquette/ : frontend, backend et docker v2 déplacés tels quels.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Yvv
2026-10-21 02:04:42 +02:00
co-authored by Claude Opus 5.5
parent 4b1732916d
commit 67ddee28b2
275 changed files with 8979 additions and 0 deletions
+65
View File
@@ -0,0 +1,65 @@
---
title: Documentation technique
description: Architecture, API et reference technique de libreDecision
---
# Documentation technique
Bienvenue dans la documentation technique de libreDecision, la plateforme de decisions collectives pour la communaute Duniter/G1.
## Presentation
libreDecision est une plateforme de gouvernance decentralisee qui permet aux membres de la Toile de Confiance (WoT) Duniter V2 de gerer des documents de reference modulaires sous vote permanent, prendre des decisions collectives multi-etapes, attribuer des mandats et archiver de maniere immuable les resultats via IPFS et la blockchain Duniter.
## Stack technique
| Couche | Technologie |
| ------------ | -------------------------------------------------- |
| Frontend | Nuxt 4 + Nuxt UI v3 + Pinia + UnoCSS |
| Backend | Python FastAPI + SQLAlchemy 2.0 (async) + Pydantic v2 |
| Base de donnees | PostgreSQL 16 (asyncpg) |
| Authentification | Duniter V2 Ed25519 challenge-response |
| Sanctuaire | IPFS (kubo) + hash on-chain (system.remark) |
| CI/CD | Woodpecker CI + Docker + Traefik |
| Temps reel | WebSocket pour les mises a jour de vote en direct |
## Historique des sprints
| Sprint | Contenu principal | Statut |
| ------ | ----------------- | ------ |
| Sprint 1 | Architecture, modeles de base (documents, items, versions, identites, protocoles, formules), API documents et authentification | Termine |
| Sprint 2 | Sanctuaire (IPFS + on-chain), gestion complete des items (CRUD, reorder), verification d'integrite, archivage de documents | Termine |
| Sprint 3 | Systeme de vote complet (sessions, votes signes, seuil WoT, criteres Smith/TechComm), meta-gouvernance, simulateur de formules, WebSocket temps reel | Termine |
| Sprint 4 | Decisions multi-etapes (workflow, avancement, vote lie), mandats (candidature, election, assignation, revocation), vote nuance | Termine |
| Sprint 5 | Stabilisation, documentation complete, deploiement production, audit securite | En cours |
## Version et statut
- **Version** : 1.0.0-rc
- **Statut** : Release candidate -- Sprint 5 (documentation et stabilisation)
- **Depot** : [git.duniter.org/tools/libredecision](https://git.duniter.org/tools/libredecision)
## Sections
### Architecture et conception
- [Architecture](/dev/architecture) -- Vue d'ensemble de l'architecture, stack, flux de communication
### Reference technique
- [Reference API](/dev/api-reference) -- Tous les endpoints REST et WebSocket avec schemas
- [Schema de base de donnees](/dev/database-schema) -- Tables, colonnes, relations et diagramme
### Domaines fonctionnels
- [Formules](/dev/formulas) -- Formules mathematiques de seuil WoT, criteres Smith/TechComm, simulateur, meta-gouvernance
- [Integration blockchain](/dev/blockchain-integration) -- Duniter V2 RPC, IPFS, ancrage on-chain
### Operations
- [Deploiement](/dev/deployment) -- Docker, Traefik, migrations, sauvegarde, mise a jour, troubleshooting
- [Securite](/dev/security) -- Authentification, integrite des votes, rate limiting, en-tetes, audit
### Contribution
- [Guide de contribution](/dev/contributing) -- Installation locale, conventions, tests, processus de contribution
@@ -0,0 +1,239 @@
# Spike : Moteur de workflow — Protocole Embarquement Forgerons
**Date** : 2026-03-02
**Statut** : Spike / Pré-étude
**Auteur** : Yvv + Claude
---
## Contexte : deux objets distincts
L'app a déjà deux concepts qui se côtoient dans "Protocoles" :
| Objet | Ce que c'est | Exemple |
|-------|-------------|---------|
| **VotingProtocol** | Règle de vote (formule, seuils, critères) | "Vote forgeron (Smith)" — D30M50B.1G.2S.1 |
| **Decision + Steps** | Processus multi-étapes (one-shot) | "Runtime Upgrade" — 5 étapes séquentielles |
Il manque un **troisième** objet : le **Protocole opérationnel réutilisable** — un template de workflow qui s'instancie pour chaque candidat/cas.
### Exemple : Embarquement Forgerons
Ce n'est pas une décision ponctuelle. C'est un **processus répétable** :
```
[Candidat] ──invite──▶ [Invitation on-chain]
│
◀──accept──
│
──setSessionKeys──▶ [Preuve technique]
│
┌──checklist aspirant (aléatoire, avec pièges)
│
├──certif smith 1 (checklist certificateur)
├──certif smith 2 (checklist certificateur)
└──certif smith 3 (checklist certificateur)
│
──goOnline──▶ [Autorité active]
```
Chaque étape a :
- Un **acteur** (candidat, certificateur, système)
- Des **prérequis** (étapes précédentes complétées)
- Une **preuve** (on-chain tx, checklist complétée, vote)
- Un **délai** (optionnel)
---
## Volume croissant prévisible
| Protocole opérationnel | Acteurs | Instances/an estimées |
|----------------------|---------|----------------------|
| Embarquement Forgerons | candidat + 3 certifieurs | ~10-50 |
| Embarquement Membre (Certification) | certifié + 5 certifieurs | ~500-2000 |
| Runtime Upgrade | CoTec + forgerons + communauté | ~4-12 |
| Modification Document | proposeur + communauté | ~10-50 |
| Mandat (élection/révocation) | candidat + communauté | ~5-20 |
| Engagement CoTec | candidat + CoTec | ~2-5 |
**Observation clé** : l'Embarquement Membre est le plus massif et partage la même structure que l'Embarquement Forgeron (checklist + certifications multiples). L'architecture doit être pensée pour ce volume.
---
## Options évaluées
### Option A : n8n (workflow automation)
**n8n** est un outil d'automatisation visuel (self-hosted, open source).
| Pour | Contre |
|------|--------|
| Éditeur visuel de workflows | Dépendance externe lourde (~500 MB Docker) |
| Webhooks, triggers, crons intégrés | Latence réseau (appels HTTP entre services) |
| 400+ intégrations (email, matrix, etc.) | Pas de MCP server configuré actuellement |
| Pas de code à écrire pour l'orchestration | Pas de concept natif de "checklist aléatoire" |
| | Les preuves on-chain nécessitent du dev custom de toute façon |
| | La communauté Duniter refuse les dépendances centralisées |
**Verdict** : n8n est excellent pour les **automations périphériques** (notifications, alertes, reporting), pas pour le **cœur du workflow**. Le cœur doit rester dans l'app.
**Usage recommandé de n8n** : connecteur optionnel pour les triggers de notification (webhook quand une étape change de statut → email/matrix/telegram). Ne pas en faire le moteur.
### Option B : Dev maison — étendre Decision/DecisionStep
Étendre le modèle `Decision` existant avec un concept de **template**.
```python
class ProcessTemplate(Base):
"""Reusable workflow template (e.g. "Embarquement Forgeron")."""
id: UUID
slug: str # "embarquement-forgeron"
name: str # "Embarquement Forgerons"
description: str
category: str # "onboarding", "governance", "upgrade"
step_templates: JSON # Ordered list of step definitions
checklist_document_id: UUID # FK to Document (engagement forgeron)
voting_protocol_id: UUID # FK to VotingProtocol
is_active: bool
class ProcessInstance(Base):
"""One execution of a template (e.g. "Embarquement de Matograine")."""
id: UUID
template_id: UUID # FK to ProcessTemplate
candidate_id: UUID # FK to DuniterIdentity (le candidat)
status: str # invited, accepted, keys_set, checklist, certifying, online, failed
current_step: int
started_at: datetime
completed_at: datetime | None
metadata: JSON # on-chain tx hashes, certifier IDs, etc.
class ProcessStepExecution(Base):
"""One step within an instance."""
id: UUID
instance_id: UUID
step_order: int
step_type: str # "on_chain", "checklist", "certification", "manual"
actor_id: UUID | None # Who must act
status: str # pending, active, completed, failed, skipped
proof: JSON | None # tx_hash, checklist_result, vote_session_id
started_at: datetime | None
completed_at: datetime | None
```
| Pour | Contre |
|------|--------|
| Zéro dépendance externe | Plus de code à écrire |
| Contrôle total sur la checklist (ordre aléatoire, pièges) | Faut designer le moteur de transitions |
| Les preuves on-chain sont natives (substrate-interface) | Le workflow avancé (timeouts, escalation) sera simpliste |
| S'intègre avec le vote engine existant | |
| La DB track tout (audit trail complet) | |
| Volume OK avec PostgreSQL (100k instances/an = rien) | |
**Verdict** : c'est la voie naturelle. Le modèle actuel `Decision + Steps` est une version simplifiée de ça. On l'étend proprement.
### Option C : Temporal.io / autre moteur de workflow distribué
| Pour | Contre |
|------|--------|
| Garanties transactionnelles fortes | Énorme pour le use case (~GB de RAM) |
| Retry/timeout/escalation natifs | Cluster Temporal = infra supplémentaire |
| Bon pour les longs workflows (jours/semaines) | Surcharge conceptuelle |
| | Aucune intégration native blockchain |
**Verdict** : overkill. À considérer uniquement si on dépasse 10 protocoles actifs avec des centaines d'instances simultanées. Pas avant 2028.
---
## Recommandation
### Sprint 2 : Option B — Dev maison, progressif
**Phase 1** (Sprint 2) — Fondations :
1. Créer `ProcessTemplate` + `ProcessInstance` + `ProcessStepExecution`
2. Seed : template "Embarquement Forgerons" avec ses 7 étapes
3. Frontend : page `/protocols/embarquement-forgerons` avec timeline visuelle
4. API : `POST /processes/{template_slug}/start` → crée une instance
5. API : `POST /processes/instances/{id}/advance` → passe à l'étape suivante
**Phase 2** (Sprint 3) — Checklist interactive :
1. UI de checklist avec ordre aléatoire + détection pièges
2. Liaison avec le Document (engagement forgeron) pour les clauses
3. Signature Ed25519 du résultat de checklist (preuve cryptographique)
**Phase 3** (Sprint 4+) — On-chain :
1. Trigger on-chain via substrate-interface (invite, accept, certify, goOnline)
2. Listener d'événements blockchain pour compléter automatiquement les étapes on-chain
3. Optionnel : webhook n8n pour notifications matrix/telegram
### Architecture cible
```
┌─────────────────────────────────────────────────┐
│ Frontend │
│ /protocols/embarquement-forgerons │
│ ├── Vue template (timeline, étapes) │
│ ├── Checklist interactive (aléatoire + pièges) │
│ └── Instance dashboard (candidats en cours) │
└──────────────────┬──────────────────────────────┘
│ API REST
┌──────────────────▼──────────────────────────────┐
│ Backend │
│ ProcessService │
│ ├── create_instance(template, candidate) │
│ ├── advance_step(instance, proof) │
│ ├── evaluate_checklist(instance, answers) │
│ └── on_chain_trigger(instance, extrinsic) │
│ │
│ SubstrateService (substrate-interface) │
│ ├── smithsMembership.invite() │
│ ├── smithsMembership.acceptInvitation() │
│ ├── smithsMembership.setSessionKeys() │
│ └── authorityMembers.goOnline() │
└──────────────────┬──────────────────────────────┘
│ Events
┌──────────────────▼──────────────────────────────┐
│ n8n (optionnel) │
│ Webhook → Notification matrix/telegram/email │
│ Cron → Relance candidats inactifs │
└─────────────────────────────────────────────────┘
```
### Ce que n8n ne fait PAS (et qu'on doit coder) :
- Checklist aléatoire avec clause piège et interruption
- Signature Ed25519 du résultat
- Appels substrate-interface (invite, certify, goOnline)
- Calcul de seuil unani-majoritaire
- Intégrité du workflow (preuve on-chain de chaque étape)
### Ce que n8n PEUT faire (optionnel, sprint 4+) :
- Webhook → notification email quand un candidat arrive à l'étape "certification"
- Cron → rappel hebdo aux certificateurs qui n'ont pas agi
- Webhook → post forum automatique quand un forgeron est accepté
- Dashboard monitoring (combien de candidats en cours, taux de completion)
---
## Nomenclature proposée dans l'UI
| Menu | Sous-section | Contenu |
|------|-------------|---------|
| **Protocoles** | Protocoles de vote | VotingProtocol (binaire, nuancé, smith, techcomm) |
| | Simulateur de formules | FormulaConfig interactif |
| | **Protocoles opérationnels** | ProcessTemplate (embarquement, upgrade, etc.) |
| **Décisions** | (inchangé) | Decision + Steps (instances one-shot) |
Les protocoles opérationnels ont leur propre section dans `/protocols` avec :
- Carte par template (nom, description, nb d'instances actives)
- Page détail : timeline template + liste d'instances en cours
- Page instance : suivi temps réel d'un candidat spécifique
---
## Prochaine étape
Valider cette orientation avec Yvv, puis :
1. Créer les 3 tables (ProcessTemplate, ProcessInstance, ProcessStepExecution)
2. Migration Alembic
3. Seed le template "Embarquement Forgerons" (7 étapes)
4. Router + service backend
5. Frontend : page template + page instance
File diff suppressed because it is too large Load Diff
+81
View File
@@ -0,0 +1,81 @@
---
title: Architecture
description: Vue d'ensemble de l'architecture technique de libreDecision
---
# Architecture
## Vue d'ensemble
libreDecision est organise en monorepo avec trois composants principaux :
```
libreDecision/
backend/ # API Python FastAPI (port 8002)
frontend/ # Application Nuxt 4 (port 3002)
docker/ # Fichiers Docker et orchestration
docs/ # Documentation (Nuxt Content)
```
## Stack technique
| Couche | Technologie |
| ------------ | -------------------------------------------------- |
| Frontend | Nuxt 4 + Nuxt UI v3 + Pinia + UnoCSS |
| Backend | Python FastAPI + SQLAlchemy 2.0 (async) + Pydantic v2 |
| Base de donnees | PostgreSQL 16 (asyncpg) |
| Authentification | Duniter V2 Ed25519 challenge-response |
| Sanctuaire | IPFS (kubo) + hash on-chain (system.remark) |
| CI/CD | Woodpecker CI + Docker + Traefik |
## Domaines fonctionnels
L'application est decoupee en 5 domaines metier, chacun avec ses modeles, schemas, routes et services :
1. **Documents** -- Documents de reference modulaires (licence, engagements, reglement) composes d'items individuels versionnables.
2. **Decisions** -- Processus decisionnels multi-etapes (qualification, examen, vote, execution, rapport).
3. **Votes** -- Sessions de vote binaire ou nuance avec formule de seuil WoT, critere Smith et critere TechComm.
4. **Mandats** -- Mandats assignes a des membres (techcomm, forgeron, personnalise) avec cycle de vie complet.
5. **Protocoles** -- Configurations de formules de vote et protocoles de vote reutilisables.
Un domaine transversal, le **Sanctuaire**, assure l'archivage immuable via IPFS et ancrage on-chain.
## Principes d'architecture
- **Async everywhere** : toute la couche donnees et HTTP est asynchrone (asyncpg, AsyncSession, FastAPI async).
- **Separation modeles / schemas / routes / services** : chaque domaine suit ce decoupage strict.
- **API versionnee** : tous les endpoints sont sous `/api/v1/`.
- **Preuve cryptographique** : chaque vote est signe avec la cle Ed25519 du votant.
- **Vote permanent** : les documents de reference sont sous vote permanent, chaque item peut etre modifie par proposition et vote.
## Schema de communication
```
Navigateur
|
v
[Nuxt 4 Frontend] -- SSR/CSR, port 3000 (prod) / 3002 (dev)
|
v (fetch /api/v1/*)
[FastAPI Backend] -- port 8002
|
+---> [PostgreSQL 16] -- Donnees relationnelles
+---> [IPFS kubo] -- Stockage distribue (Sanctuaire)
+---> [Duniter V2 RPC] -- WoT, Smith, TechComm, system.remark
```
## Flux d'authentification
1. Le client envoie son adresse Duniter SS58 via `POST /api/v1/auth/challenge`.
2. Le serveur genere un challenge aleatoire (64 hex) et le stocke en memoire (TTL 5 min).
3. Le client signe le challenge avec sa cle privee Ed25519 et soumet via `POST /api/v1/auth/verify`.
4. Le serveur verifie la signature, cree ou retrouve l'identite `DuniterIdentity`, et retourne un token de session.
5. Le token est utilise en header `Authorization: Bearer <token>` pour les requetes authentifiees.
## Flux de vote
1. Un protocole de vote et sa formule sont crees ou selectionnes.
2. Une session de vote est creee avec un snapshot des tailles WoT/Smith/TechComm.
3. Les membres votent (binaire ou nuance) avec signature cryptographique.
4. A la cloture, le seuil WoT est calcule, les criteres Smith et TechComm sont verifies.
5. Le resultat (adopte/rejete) est archive dans le Sanctuaire (IPFS + on-chain).
+648
View File
@@ -0,0 +1,648 @@
---
title: Reference API
description: Liste des endpoints de l'API libreDecision
---
# Reference API
Tous les endpoints sont prefixes par `/api/v1`. L'API est auto-documentee via OpenAPI/Swagger a l'adresse `/docs` en mode debug.
## Authentification (`/api/v1/auth`)
| Methode | Endpoint | Description | Auth |
| ------- | ------------- | ----------------------------------------------------- | ---- |
| POST | `/challenge` | Generer un challenge Ed25519 pour une adresse Duniter | Non |
| POST | `/verify` | Verifier la signature du challenge et obtenir un token | Non |
| GET | `/me` | Retourner l'identite authentifiee courante | Oui |
| POST | `/logout` | Invalider la session courante | Oui |
## Documents (`/api/v1/documents`)
| Methode | Endpoint | Description | Auth |
| ------- | -------------------------------------- | ---------------------------------------- | ---- |
| GET | `/` | Lister les documents (filtres: doc_type, status) | Non |
| POST | `/` | Creer un nouveau document | Oui |
| GET | `/{slug}` | Obtenir un document par son slug | Non |
| PUT | `/{slug}` | Mettre a jour un document | Oui |
| POST | `/{slug}/items` | Ajouter un item au document | Oui |
| GET | `/{slug}/items` | Lister les items d'un document | Non |
| GET | `/{slug}/items/{item_id}` | Obtenir un item avec son historique | Non |
| PUT | `/{slug}/items/{item_id}` | Mettre a jour un item (titre, texte, position, type) | Oui |
| DELETE | `/{slug}/items/{item_id}` | Supprimer un item du document | Oui |
| POST | `/{slug}/items/{item_id}/versions` | Proposer une nouvelle version d'un item | Oui |
| GET | `/{slug}/items/{item_id}/versions` | Lister les versions d'un item | Non |
| PUT | `/{slug}/items/{item_id}/versions/{version_id}/accept` | Accepter une version proposee (applique le texte a l'item) | Oui |
| PUT | `/{slug}/items/{item_id}/versions/{version_id}/reject` | Rejeter une version proposee | Oui |
| PUT | `/{slug}/items/reorder` | Reordonner les items d'un document | Oui |
| POST | `/{slug}/archive` | Archiver le document dans le sanctuaire (hash SHA-256 + IPFS + on-chain) | Oui |
## Decisions (`/api/v1/decisions`)
| Methode | Endpoint | Description | Auth |
| ------- | ---------------- | ------------------------------------------------ | ---- |
| GET | `/` | Lister les decisions (filtres: decision_type, status) | Non |
| POST | `/` | Creer une nouvelle decision | Oui |
| GET | `/{id}` | Obtenir une decision avec ses etapes | Non |
| PUT | `/{id}` | Mettre a jour une decision | Oui |
| DELETE | `/{id}` | Supprimer une decision (brouillon uniquement) | Oui |
| POST | `/{id}/steps` | Ajouter une etape a une decision | Oui |
| POST | `/{id}/advance` | Avancer la decision a l'etape suivante | Oui |
| POST | `/{id}/steps/{step_id}/create-vote-session` | Creer une session de vote pour une etape | Oui |
## Votes (`/api/v1/votes`)
| Methode | Endpoint | Description | Auth |
| ------- | ------------------------------- | -------------------------------------------- | ---- |
| POST | `/sessions` | Creer une session de vote | Oui |
| GET | `/sessions` | Lister les sessions de vote (filtres: status, protocol_id, decision_id) | Non |
| GET | `/sessions/{id}` | Obtenir une session de vote | Non |
| POST | `/sessions/{id}/vote` | Soumettre un vote (signe) | Oui |
| GET | `/sessions/{id}/votes` | Lister les votes d'une session | Non |
| GET | `/sessions/{id}/result` | Calculer et retourner le resultat courant | Non |
| POST | `/sessions/{id}/close` | Cloturer la session et calculer le resultat final | Oui |
| GET | `/sessions/{id}/threshold` | Obtenir le detail du calcul de seuil (formule, parametres, valeurs intermediaires) | Non |
| POST | `/sessions/{id}/tally` | Forcer un recomptage des votes et recalcul du seuil | Oui |
## Mandats (`/api/v1/mandates`)
| Methode | Endpoint | Description | Auth |
| ------- | ----------------- | ---------------------------------------------- | ---- |
| GET | `/` | Lister les mandats (filtres: mandate_type, status) | Non |
| POST | `/` | Creer un nouveau mandat | Oui |
| GET | `/{id}` | Obtenir un mandat avec ses etapes | Non |
| PUT | `/{id}` | Mettre a jour un mandat | Oui |
| DELETE | `/{id}` | Supprimer un mandat (brouillon uniquement) | Oui |
| POST | `/{id}/steps` | Ajouter une etape a un mandat | Oui |
| GET | `/{id}/steps` | Lister les etapes d'un mandat | Non |
| POST | `/{id}/advance` | Avancer le mandat a l'etape suivante | Oui |
| POST | `/{id}/assign` | Assigner un mandataire au mandat | Oui |
| POST | `/{id}/revoke` | Revoquer un mandat actif | Oui |
| POST | `/{id}/steps/{step_id}/create-vote-session` | Creer une session de vote pour une etape | Oui |
## Protocoles (`/api/v1/protocols`)
| Methode | Endpoint | Description | Auth |
| ------- | ------------------- | -------------------------------------------------- | ---- |
| GET | `/` | Lister les protocoles de vote | Non |
| POST | `/` | Creer un protocole de vote | Oui |
| GET | `/{id}` | Obtenir un protocole avec sa configuration formule | Non |
| PUT | `/{id}` | Mettre a jour un protocole (meta-gouvernance) | Oui |
| GET | `/formulas` | Lister les configurations de formules | Non |
| POST | `/formulas` | Creer une configuration de formule | Oui |
| GET | `/formulas/{id}` | Obtenir une configuration de formule par son ID | Non |
| PUT | `/formulas/{id}` | Mettre a jour une configuration de formule (meta-gouvernance) | Oui |
| POST | `/simulate` | Simuler le calcul d'une formule avec des parametres arbitraires | Non |
## Sanctuaire (`/api/v1/sanctuary`)
| Methode | Endpoint | Description | Auth |
| ------- | --------------------------------- | ---------------------------------------------------------- | ---- |
| GET | `/` | Lister les entrees du sanctuaire (filtre: entry_type) | Non |
| GET | `/{id}` | Obtenir une entree du sanctuaire | Non |
| POST | `/` | Creer une entree (hash SHA-256, CID IPFS, TX on-chain) | Oui |
| GET | `/{id}/verify` | Verifier l'integrite d'une entree (recalcul SHA-256, verification IPFS et on-chain) | Non |
| GET | `/by-reference/{reference_id}` | Obtenir les entrees liees a une entite source par son UUID | Non |
## WebSocket (`/api/v1/ws`)
| Endpoint | Description |
| --------- | -------------------------------------------------------- |
| `/ws` | Connexion WebSocket pour notifications temps reel (votes, decisions) |
## Sante
| Methode | Endpoint | Description |
| ------- | -------------- | -------------------------- |
| GET | `/api/health` | Verification de sante (hors versionning) |
## Details des endpoints Sprint 2
### `PUT /api/v1/documents/{slug}/items/{item_id}` -- Mettre a jour un item
Met a jour les champs d'un item existant (titre, texte courant, position, type). Seuls les champs fournis sont mis a jour (mise a jour partielle).
**Corps de la requete** (tous les champs sont optionnels) :
```json
{
"title": "Nouveau titre",
"current_text": "Texte mis a jour...",
"position": "2.1",
"item_type": "rule"
}
```
**Reponse** : `200 OK` avec l'item mis a jour (`DocumentItemOut`).
---
### `DELETE /api/v1/documents/{slug}/items/{item_id}` -- Supprimer un item
Supprime un item d'un document. La suppression est en cascade : toutes les versions associees sont egalement supprimees.
**Reponse** : `204 No Content`.
---
### `GET /api/v1/documents/{slug}/items/{item_id}/versions` -- Lister les versions d'un item
Retourne l'historique complet des versions proposees pour un item, ordonne par date de creation decroissante.
**Parametres de requete** : `skip`, `limit` (pagination standard).
**Reponse** : `200 OK` avec une liste de `ItemVersionOut`.
---
### `PUT /api/v1/documents/{slug}/items/{item_id}/versions/{version_id}/accept` -- Accepter une version
Accepte une version proposee. Le texte propose remplace le texte courant de l'item. Toutes les autres versions en statut `proposed` ou `voting` pour cet item sont automatiquement rejetees.
**Reponse** : `200 OK` avec la version mise a jour (`ItemVersionOut`, statut `accepted`).
---
### `PUT /api/v1/documents/{slug}/items/{item_id}/versions/{version_id}/reject` -- Rejeter une version
Rejette une version proposee. Le texte courant de l'item reste inchange.
**Reponse** : `200 OK` avec la version mise a jour (`ItemVersionOut`, statut `rejected`).
---
### `PUT /api/v1/documents/{slug}/items/reorder` -- Reordonner les items
Modifie l'ordre d'affichage des items dans un document en mettant a jour le champ `sort_order` de chaque item.
**Corps de la requete** :
```json
{
"items": [
{ "item_id": "uuid-1", "sort_order": 0 },
{ "item_id": "uuid-2", "sort_order": 1 },
{ "item_id": "uuid-3", "sort_order": 2 }
]
}
```
**Reponse** : `200 OK` avec la liste des items reordonnes.
---
### `POST /api/v1/documents/{slug}/archive` -- Archiver un document
Archive le document complet dans le sanctuaire. Le processus :
1. Le contenu integral du document (metadonnees + items) est serialise.
2. Un hash SHA-256 est calcule sur le contenu.
3. Le contenu est envoye sur IPFS (CID retourne).
4. Le hash est ancre on-chain via `system.remark` sur Duniter V2.
5. Une entree `sanctuary_entries` est creee avec les references.
6. Le statut du document passe a `archived` et les champs `ipfs_cid` et `chain_anchor` sont mis a jour.
**Reponse** : `200 OK` avec le document mis a jour incluant `ipfs_cid` et `chain_anchor`.
---
### `GET /api/v1/sanctuary/{id}/verify` -- Verifier l'integrite d'une entree
Verifie l'integrite d'une entree du sanctuaire en effectuant trois controles :
1. **Hash SHA-256** : recalcul du hash a partir du contenu source et comparaison avec `content_hash`.
2. **IPFS** : verification que le CID IPFS pointe vers un contenu valide (si disponible).
3. **On-chain** : verification que le hash est present dans le `system.remark` du bloc reference (si disponible).
**Reponse** :
```json
{
"entry_id": "uuid",
"hash_valid": true,
"ipfs_valid": true,
"chain_valid": true,
"verified_at": "2026-02-28T12:00:00Z",
"details": "Tous les controles sont valides."
}
```
---
### `GET /api/v1/sanctuary/by-reference/{reference_id}` -- Entrees par reference
Retourne toutes les entrees du sanctuaire liees a une entite source (document, decision ou session de vote) identifiee par son UUID.
**Parametres de requete** : `skip`, `limit` (pagination standard).
**Reponse** : `200 OK` avec une liste de `SanctuaryEntryOut`.
## Details des endpoints Sprint 3
### `GET /api/v1/votes/sessions` -- Lister les sessions de vote
Retourne la liste des sessions de vote avec pagination et filtres optionnels.
**Parametres de requete** :
| Parametre | Type | Description |
| ------------- | ------ | ---------------------------------------------- |
| `status` | string | Filtrer par statut (`open`, `closed`, `draft`) |
| `protocol_id` | uuid | Filtrer par protocole de vote |
| `decision_id` | uuid | Filtrer par decision associee |
| `skip` | int | Offset de pagination (defaut 0) |
| `limit` | int | Nombre max de resultats (defaut 50, max 200) |
**Reponse** : `200 OK` avec une liste de `VoteSessionOut`.
---
### `POST /api/v1/votes/sessions/{id}/close` -- Cloturer une session
Cloture une session de vote ouverte et calcule le resultat final. Le calcul inclut la formule WoT, les criteres Smith et TechComm le cas echeant. La session passe au statut `closed` et le resultat est fige.
**Preconditions** :
- La session doit etre au statut `open`.
- L'utilisateur doit etre le createur de la session ou un membre du Comite Technique.
**Reponse** :
```json
{
"session_id": "uuid",
"status": "closed",
"closed_at": "2026-02-28T12:00:00Z",
"result": {
"votes_for": 97,
"votes_against": 23,
"total_votes": 120,
"wot_size": 7224,
"threshold": 94,
"adopted": true,
"smith_threshold": 2,
"smith_votes_for": 5,
"smith_met": true,
"techcomm_threshold": null,
"techcomm_votes_for": null,
"techcomm_met": null
}
}
```
---
### `GET /api/v1/votes/sessions/{id}/threshold` -- Detail du calcul de seuil
Retourne le detail complet du calcul de seuil pour une session de vote, incluant les valeurs intermediaires de la formule. Utile pour la transparence et le debug.
**Reponse** :
```json
{
"session_id": "uuid",
"formula_config_id": "uuid",
"mode_params": "D30M50B.1G.2",
"parameters": {
"constant_base": 0.0,
"base_exponent": 0.1,
"majority_pct": 50,
"gradient_exponent": 0.2,
"smith_exponent": null,
"techcomm_exponent": null
},
"inputs": {
"wot_size": 7224,
"total_votes": 120,
"smith_wot_size": 20,
"techcomm_size": 5
},
"computation": {
"base_term": 7.943e-08,
"participation_ratio": 0.0166,
"participation_ratio_powered": 0.4217,
"inertia_factor": 0.7892,
"effective_threshold": 94.70,
"threshold_ceiled": 95,
"threshold_final": 94
},
"criteria": {
"smith_threshold": null,
"techcomm_threshold": null
}
}
```
---
### `POST /api/v1/votes/sessions/{id}/tally` -- Forcer un recomptage
Force le recalcul du decompte des votes et du seuil pour une session. Utile si des votes ont ete invalides manuellement ou si la taille WoT a ete corrigee.
**Preconditions** :
- L'utilisateur doit etre le createur de la session ou un membre du Comite Technique.
**Corps de la requete** (optionnel) :
```json
{
"wot_size_override": 7250,
"smith_wot_size_override": null,
"techcomm_size_override": null
}
```
Si aucun corps n'est fourni, les tailles sont reprises du snapshot initial.
**Reponse** : `200 OK` avec le meme format que le resultat de cloture.
---
### `PUT /api/v1/protocols/{id}` -- Mettre a jour un protocole
Met a jour un protocole de vote existant. Les modifications de protocoles actifs (utilises par des sessions ouvertes) sont soumises a **meta-gouvernance** : une session de vote doit d'abord valider la modification.
**Corps de la requete** (champs optionnels) :
```json
{
"name": "Nouveau nom du protocole",
"description": "Description mise a jour",
"vote_type": "nuanced",
"duration_days": 60,
"formula_config_id": "uuid-de-la-nouvelle-formule"
}
```
**Reponse** : `200 OK` avec le protocole mis a jour (`ProtocolOut`).
**Code 409** : Si le protocole est utilise par une session ouverte et qu'aucune validation meta-gouvernance n'est fournie.
---
### `GET /api/v1/protocols/formulas/{id}` -- Obtenir une formule
Retourne une configuration de formule par son identifiant, incluant tous les parametres et le `mode_params` encode.
**Reponse** : `200 OK` avec un `FormulaConfigOut`.
---
### `PUT /api/v1/protocols/formulas/{id}` -- Mettre a jour une formule
Met a jour une configuration de formule existante. Comme pour les protocoles, les formules actives sont protegees par la meta-gouvernance.
**Corps de la requete** (champs optionnels) :
```json
{
"name": "Configuration amendee",
"majority_pct": 66,
"base_exponent": 0.05,
"gradient_exponent": 0.3,
"constant_base": 0.0,
"smith_exponent": 0.1,
"techcomm_exponent": 0.1,
"ratio_multiplier": null,
"is_ratio_mode": false
}
```
Le champ `mode_params` est recalcule automatiquement a partir des valeurs fournies.
**Reponse** : `200 OK` avec la formule mise a jour (`FormulaConfigOut`).
**Code 409** : Si la formule est liee a un protocole actif sans validation meta-gouvernance.
---
### `POST /api/v1/protocols/simulate` -- Simuler une formule
Simule le calcul de seuil d'une formule avec des parametres arbitraires, sans creer ni modifier aucune donnee. Endpoint ouvert (pas d'authentification requise) pour permettre l'experimentation.
**Corps de la requete** :
```json
{
"wot_size": 7224,
"total_votes": 120,
"majority_pct": 50,
"base_exponent": 0.1,
"gradient_exponent": 0.2,
"constant_base": 0.0,
"smith_wot_size": 20,
"smith_exponent": 0.1,
"techcomm_size": 5,
"techcomm_exponent": 0.1,
"votes_for": 97
}
```
**Reponse** :
```json
{
"threshold": 94,
"inertia_factor": 0.7892,
"participation_ratio": 0.0166,
"base_term": 7.943e-08,
"smith_threshold": 2,
"techcomm_threshold": 2,
"adopted": true,
"details": {
"wot_threshold_met": true,
"smith_met": true,
"techcomm_met": true
}
}
```
## Details des endpoints Sprint 4
### `POST /api/v1/decisions/{id}/advance` -- Avancer une decision
Avance une decision a l'etape suivante de son workflow. Complete l'etape active courante et active la prochaine etape en attente. Si toutes les etapes sont terminees, le statut global de la decision avance au statut suivant dans le cycle de vie (`draft` -> `qualification` -> `review` -> `voting` -> `executed` -> `closed`).
**Comportement** :
- S'il y a une etape `active` : elle passe a `completed`, la prochaine etape `pending` est activee.
- S'il n'y a pas d'etape active : la premiere etape `pending` est activee. Si la decision est en `draft`, elle passe a `qualification`.
- Si toutes les etapes sont terminees : le statut global avance.
**Preconditions** :
- La decision ne doit pas etre au statut `closed`.
- L'utilisateur doit etre authentifie.
**Reponse** : `200 OK` avec un `DecisionAdvanceOut` :
```json
{
"id": "uuid",
"title": "Runtime upgrade v3.2.0",
"description": "...",
"context": "...",
"decision_type": "runtime_upgrade",
"status": "review",
"voting_protocol_id": "uuid",
"created_by_id": "uuid",
"created_at": "2026-02-28T10:00:00Z",
"updated_at": "2026-02-28T12:00:00Z",
"steps": [
{"id": "uuid", "step_order": 0, "step_type": "qualification", "status": "completed", "...": "..."},
{"id": "uuid", "step_order": 1, "step_type": "review", "status": "active", "...": "..."},
{"id": "uuid", "step_order": 2, "step_type": "vote", "status": "pending", "...": "..."}
],
"message": "Etape 'Qualification' completee. Etape 'Examen' activee."
}
```
**Code 400** : Si la decision est deja cloturee ou qu'aucun avancement n'est possible.
---
### `DELETE /api/v1/decisions/{id}` -- Supprimer une decision
Supprime une decision. La suppression n'est autorisee que si la decision est au statut `draft`. Les etapes associees sont supprimees en cascade.
**Preconditions** :
- La decision doit etre au statut `draft`.
- L'utilisateur doit etre authentifie.
**Reponse** : `204 No Content`.
**Code 400** : Si la decision n'est pas au statut `draft`.
---
### `POST /api/v1/decisions/{id}/steps/{step_id}/create-vote-session` -- Creer une session de vote pour une etape de decision
Cree une session de vote liee a une etape specifique d'une decision. La session est configuree automatiquement a partir du protocole de vote de la decision.
**Comportement** :
1. Verifie que la decision et l'etape existent.
2. Verifie que l'etape est de type `vote` et au statut `active`.
3. Cree une session de vote avec le protocole de la decision.
4. Fait un snapshot des tailles WoT, Smith et TechComm.
5. Calcule la date de fin a partir de la duree de la formule.
6. Rattache la session a l'etape via `vote_session_id`.
**Reponse** : `201 Created` avec un `VoteSessionOut`.
**Code 400** : Si l'etape n'est pas de type `vote`, n'est pas `active`, ou si une session est deja liee.
---
### `POST /api/v1/mandates/{id}/advance` -- Avancer un mandat
Avance un mandat a l'etape suivante de son workflow. Fonctionne de maniere identique a l'avancement des decisions, avec le cycle de vie specifique aux mandats (`draft` -> `candidacy` -> `voting` -> `active` -> `reporting` -> `completed`).
**Comportement** :
- S'il y a une etape `active` : elle passe a `completed`, la prochaine etape `pending` est activee.
- S'il n'y a pas d'etape active : la premiere etape `pending` est activee. Si le mandat est en `draft`, il passe a `candidacy`.
- Si toutes les etapes sont terminees : le statut global avance.
**Preconditions** :
- Le mandat ne doit pas etre au statut `completed` ou `revoked`.
- L'utilisateur doit etre authentifie.
**Reponse** : `200 OK` avec un `MandateAdvanceOut` :
```json
{
"id": "uuid",
"title": "Membre Comite Technique 2026",
"description": "...",
"mandate_type": "techcomm",
"status": "candidacy",
"mandatee_id": null,
"decision_id": null,
"starts_at": null,
"ends_at": null,
"created_at": "2026-02-28T10:00:00Z",
"updated_at": "2026-02-28T12:00:00Z",
"steps": [
{"id": "uuid", "step_order": 0, "step_type": "formulation", "status": "completed", "...": "..."},
{"id": "uuid", "step_order": 1, "step_type": "candidacy", "status": "active", "...": "..."}
],
"message": "Etape 'Formulation' completee. Etape 'Candidature' activee."
}
```
**Code 400** : Si le mandat est deja en statut terminal (`completed` ou `revoked`).
---
### `POST /api/v1/mandates/{id}/assign` -- Assigner un mandataire
Assigne un mandataire (membre elu) a un mandat apres un vote reussi.
**Corps de la requete** :
```json
{
"mandatee_id": "uuid-de-l-identite-duniter"
}
```
**Comportement** :
1. Verifie que le mandat existe et qu'il est dans un statut permettant l'assignation.
2. Definit le `mandatee_id` du mandat.
3. Le mandat peut ensuite etre avance vers le statut `active`.
**Preconditions** :
- L'utilisateur doit etre authentifie.
- Le `mandatee_id` doit correspondre a une identite Duniter existante.
**Reponse** : `200 OK` avec un `MandateOut` mis a jour.
**Code 404** : Si le mandat ou l'identite Duniter n'existe pas.
---
### `POST /api/v1/mandates/{id}/revoke` -- Revoquer un mandat
Revoque un mandat actif de maniere anticipee. Le statut du mandat passe directement a `revoked`, mettant fin aux responsabilites du mandataire.
**Preconditions** :
- Le mandat doit etre au statut `active` ou `reporting`.
- L'utilisateur doit etre authentifie.
**Reponse** : `200 OK` avec un `MandateOut` mis a jour (statut `revoked`).
**Code 400** : Si le mandat n'est pas dans un statut permettant la revocation.
---
### `POST /api/v1/mandates/{id}/steps/{step_id}/create-vote-session` -- Creer une session de vote pour une etape de mandat
Cree une session de vote liee a une etape specifique d'un mandat. Fonctionne de maniere identique a la creation de session pour les decisions.
**Comportement** :
1. Verifie que le mandat et l'etape existent.
2. Verifie que l'etape est de type `vote` et au statut `active`.
3. Cree une session de vote avec le protocole associe.
4. Fait un snapshot des tailles WoT, Smith et TechComm.
5. Calcule la date de fin a partir de la duree de la formule.
6. Rattache la session a l'etape via `vote_session_id`.
**Reponse** : `201 Created` avec un `VoteSessionOut`.
**Code 400** : Si l'etape n'est pas de type `vote`, n'est pas `active`, ou si une session est deja liee.
---
## Pagination
Les endpoints de liste acceptent les parametres `skip` (offset, defaut 0) et `limit` (max 200, defaut 50).
## Authentification
Les endpoints marques "Oui" dans la colonne Auth requierent un header :
```
Authorization: Bearer <token>
```
Le token est obtenu via le flux challenge-response (`/auth/challenge` puis `/auth/verify`).
+328
View File
@@ -0,0 +1,328 @@
---
title: Schema de base de donnees
description: Tables et relations de la base de donnees PostgreSQL
---
# Schema de base de donnees
libreDecision utilise PostgreSQL 16 avec SQLAlchemy 2.0 en mode asynchrone (asyncpg). Toutes les cles primaires sont des UUID v4.
## Tables
### `duniter_identities`
Identites Duniter V2 connues de la plateforme.
| Colonne | Type | Description |
| -------------- | ------------ | ---------------------------------------------- |
| id | UUID (PK) | Identifiant unique |
| address | VARCHAR(64) | Adresse SS58 Duniter (unique, indexee) |
| display_name | VARCHAR(128) | Nom d'affichage |
| wot_status | VARCHAR(32) | Statut WoT : member, pending, revoked, unknown |
| is_smith | BOOLEAN | Membre Smith (forgeron) |
| is_techcomm | BOOLEAN | Membre du Comite Technique |
| created_at | TIMESTAMPTZ | Date de creation |
| updated_at | TIMESTAMPTZ | Date de derniere mise a jour |
### `sessions`
Sessions d'authentification (tokens).
| Colonne | Type | Description |
| ------------ | ------------ | ---------------------------------- |
| id | UUID (PK) | Identifiant unique |
| token_hash | VARCHAR(128) | Hash du token (unique, indexe) |
| identity_id | UUID (FK) | -> duniter_identities.id |
| created_at | TIMESTAMPTZ | Date de creation |
| expires_at | TIMESTAMPTZ | Date d'expiration |
### `documents`
Documents de reference modulaires. Le cycle de vie d'un document suit les statuts `draft` -> `active` -> `archived`. Lors de l'archivage (Sprint 2), les champs `ipfs_cid` et `chain_anchor` sont renseignes automatiquement par le service sanctuaire.
| Colonne | Type | Description |
| ------------ | ------------ | ----------------------------------------------------- |
| id | UUID (PK) | Identifiant unique |
| slug | VARCHAR(128) | Identifiant lisible (unique, indexe) |
| title | VARCHAR(256) | Titre du document |
| doc_type | VARCHAR(64) | Type : licence, engagement, reglement, constitution |
| version | VARCHAR(32) | Version semantique (defaut "0.1.0") |
| status | VARCHAR(32) | Statut : draft, active, archived |
| description | TEXT | Description du document |
| ipfs_cid | VARCHAR(128) | CID IPFS de la derniere version archivee (renseigne lors de l'archivage) |
| chain_anchor | VARCHAR(128) | Hash de transaction on-chain (renseigne lors de l'archivage) |
| created_at | TIMESTAMPTZ | Date de creation |
| updated_at | TIMESTAMPTZ | Date de derniere mise a jour |
### `document_items`
Items individuels composant un document (clauses, regles, verifications, etc.). Chaque item peut etre modifie, supprime ou reordonne individuellement (Sprint 2). Le champ `current_text` est mis a jour automatiquement lorsqu'une version est acceptee.
| Colonne | Type | Description |
| ------------------- | ------------ | ------------------------------------------------- |
| id | UUID (PK) | Identifiant unique |
| document_id | UUID (FK) | -> documents.id (cascade delete) |
| position | VARCHAR(16) | Numero de position ("1", "1.1", "3.2") |
| item_type | VARCHAR(32) | Type : clause, rule, verification, preamble, section |
| title | VARCHAR(256) | Titre de l'item |
| current_text | TEXT | Texte courant de l'item (mis a jour lors de l'acceptation d'une version) |
| voting_protocol_id | UUID (FK) | -> voting_protocols.id (protocole specifique) |
| sort_order | INTEGER | Ordre de tri (modifiable via endpoint reorder) |
| created_at | TIMESTAMPTZ | Date de creation |
| updated_at | TIMESTAMPTZ | Date de derniere mise a jour |
### `item_versions`
Historique des versions proposees pour chaque item. Lors de l'acceptation d'une version (Sprint 2), le `current_text` de l'item parent est remplace par le `proposed_text` de la version, et toutes les autres versions en attente (`proposed`, `voting`) sont automatiquement rejetees.
| Colonne | Type | Description |
| -------------- | ------------ | ------------------------------------------------------ |
| id | UUID (PK) | Identifiant unique |
| item_id | UUID (FK) | -> document_items.id (cascade delete) |
| proposed_text | TEXT | Texte propose |
| diff_text | TEXT | Diff unifie entre texte courant et propose (genere automatiquement) |
| rationale | TEXT | Justification de la modification |
| status | VARCHAR(32) | Statut : proposed, voting, accepted, rejected |
| decision_id | UUID (FK) | -> decisions.id (decision associee) |
| proposed_by_id | UUID (FK) | -> duniter_identities.id (auteur de la proposition) |
| created_at | TIMESTAMPTZ | Date de creation |
### `decisions`
Processus decisionnels multi-etapes. Le cycle de vie suit un ordre strict de statuts gere par le service `decision_service.advance_decision()`.
| Colonne | Type | Description |
| ------------------- | ------------ | -------------------------------------------------------- |
| id | UUID (PK) | Identifiant unique |
| title | VARCHAR(256) | Titre de la decision |
| description | TEXT | Description |
| context | TEXT | Contexte additionnel (cadrage) |
| decision_type | VARCHAR(64) | Type : runtime_upgrade, document_change, mandate_vote, parameter_change, custom |
| status | VARCHAR(32) | Statut : draft, qualification, review, voting, executed, closed |
| voting_protocol_id | UUID (FK) | -> voting_protocols.id |
| created_by_id | UUID (FK) | -> duniter_identities.id |
| created_at | TIMESTAMPTZ | Date de creation |
| updated_at | TIMESTAMPTZ | Date de derniere mise a jour |
**Workflow des statuts** : `draft` -> `qualification` -> `review` -> `voting` -> `executed` -> `closed`. La transition est geree par le service `advance_decision` qui complete l'etape active, active la suivante, et avance le statut global quand toutes les etapes sont terminees. La suppression n'est autorisee qu'en statut `draft`.
### `decision_steps`
Etapes d'un processus decisionnel. Chaque etape est traitee sequentiellement selon `step_order`. Le service `advance_decision` complete l'etape `active` et active la prochaine `pending`.
| Colonne | Type | Description |
| ---------------- | ------------ | -------------------------------------------------------- |
| id | UUID (PK) | Identifiant unique |
| decision_id | UUID (FK) | -> decisions.id (cascade delete) |
| step_order | INTEGER | Ordre de l'etape (0-indexed, determine la sequence) |
| step_type | VARCHAR(32) | Type : qualification, review, vote, execution, reporting |
| title | VARCHAR(256) | Titre de l'etape |
| description | TEXT | Description |
| status | VARCHAR(32) | Statut : pending, active, completed, skipped |
| vote_session_id | UUID (FK) | -> vote_sessions.id (renseigne via create-vote-session pour les etapes de type `vote`) |
| outcome | TEXT | Resultat de l'etape |
| created_at | TIMESTAMPTZ | Date de creation |
**Types d'etapes** : `qualification` (verification recevabilite), `review` (examen communautaire), `vote` (session de vote formelle), `execution` (mise en oeuvre), `reporting` (compte-rendu). Les etapes de type `vote` sont les seules a pouvoir avoir un `vote_session_id` renseigne.
### `vote_sessions`
Sessions de vote avec snapshot des tailles WoT et decompte en temps reel.
| Colonne | Type | Description |
| ------------------- | ------------ | ---------------------------------------------- |
| id | UUID (PK) | Identifiant unique |
| decision_id | UUID (FK) | -> decisions.id |
| item_version_id | UUID (FK) | -> item_versions.id |
| voting_protocol_id | UUID (FK) | -> voting_protocols.id |
| wot_size | INTEGER | Taille WoT au debut de la session |
| smith_size | INTEGER | Taille Smith au debut de la session |
| techcomm_size | INTEGER | Taille TechComm au debut de la session |
| starts_at | TIMESTAMPTZ | Date de debut |
| ends_at | TIMESTAMPTZ | Date de fin |
| status | VARCHAR(32) | Statut : open, closed, tallied |
| votes_for | INTEGER | Nombre de votes pour |
| votes_against | INTEGER | Nombre de votes contre |
| votes_total | INTEGER | Nombre total de votes |
| smith_votes_for | INTEGER | Votes pour des membres Smith |
| techcomm_votes_for | INTEGER | Votes pour des membres TechComm |
| threshold_required | FLOAT | Seuil calcule requis |
| result | VARCHAR(32) | Resultat : adopted, rejected, null |
| chain_recorded | BOOLEAN | Enregistre sur la blockchain |
| chain_tx_hash | VARCHAR(128) | Hash de la transaction on-chain |
| created_at | TIMESTAMPTZ | Date de creation |
### `votes`
Votes individuels avec preuve cryptographique.
| Colonne | Type | Description |
| ---------------- | ------------ | -------------------------------------------- |
| id | UUID (PK) | Identifiant unique |
| session_id | UUID (FK) | -> vote_sessions.id |
| voter_id | UUID (FK) | -> duniter_identities.id |
| vote_value | VARCHAR(32) | Valeur : for, against, ou niveau nuance |
| nuanced_level | INTEGER | Niveau nuance (0-5) pour les votes nuances |
| comment | TEXT | Commentaire optionnel |
| signature | TEXT | Signature Ed25519 du payload |
| signed_payload | TEXT | Payload signe (pour verification) |
| voter_wot_status | VARCHAR(32) | Statut WoT du votant au moment du vote |
| voter_is_smith | BOOLEAN | Le votant est-il forgeron |
| voter_is_techcomm| BOOLEAN | Le votant est-il membre TechComm |
| is_active | BOOLEAN | Vote actif (false si remplace) |
| created_at | TIMESTAMPTZ | Date de creation |
### `mandates`
Mandats assignes a des membres. Le cycle de vie suit un ordre strict de statuts gere par le service `mandate_service.advance_mandate()`. La revocation (`POST /mandates/{id}/revoke`) permet de passer directement au statut terminal `revoked`.
| Colonne | Type | Description |
| ------------- | ------------ | ------------------------------------------------------- |
| id | UUID (PK) | Identifiant unique |
| title | VARCHAR(256) | Titre du mandat |
| description | TEXT | Description |
| mandate_type | VARCHAR(64) | Type : techcomm, smith, custom |
| status | VARCHAR(32) | Statut : draft, candidacy, voting, active, reporting, completed, revoked |
| mandatee_id | UUID (FK) | -> duniter_identities.id (titulaire du mandat, renseigne via `POST /mandates/{id}/assign`) |
| decision_id | UUID (FK) | -> decisions.id (decision associee, optionnel) |
| starts_at | TIMESTAMPTZ | Date de debut (renseignee lors de l'assignation) |
| ends_at | TIMESTAMPTZ | Date de fin (renseignee lors de l'assignation) |
| created_at | TIMESTAMPTZ | Date de creation |
| updated_at | TIMESTAMPTZ | Date de derniere mise a jour |
**Workflow des statuts** : `draft` -> `candidacy` -> `voting` -> `active` -> `reporting` -> `completed`. Les statuts terminaux sont `completed` et `revoked`. La suppression n'est autorisee qu'en statut `draft`. La revocation est possible depuis les statuts `active` ou `reporting`.
### `mandate_steps`
Etapes du cycle de vie d'un mandat. Chaque etape est traitee sequentiellement selon `step_order`. Le service `advance_mandate` complete l'etape `active` et active la prochaine `pending`.
| Colonne | Type | Description |
| ---------------- | ------------ | ------------------------------------------------------------- |
| id | UUID (PK) | Identifiant unique |
| mandate_id | UUID (FK) | -> mandates.id (cascade delete) |
| step_order | INTEGER | Ordre de l'etape (0-indexed, determine la sequence) |
| step_type | VARCHAR(32) | Type : formulation, candidacy, vote, assignment, reporting, completion, revocation |
| title | VARCHAR(256) | Titre de l'etape |
| description | TEXT | Description |
| status | VARCHAR(32) | Statut : pending, active, completed, skipped |
| vote_session_id | UUID (FK) | -> vote_sessions.id (renseigne via create-vote-session pour les etapes de type `vote`) |
| outcome | TEXT | Resultat de l'etape |
| created_at | TIMESTAMPTZ | Date de creation |
**Types d'etapes** : `formulation` (definition du mandat), `candidacy` (depot des candidatures), `vote` (election par vote WoT), `assignment` (attribution au candidat elu), `reporting` (compte-rendu), `completion` (fin normale), `revocation` (fin anticipee). Les etapes de type `vote` sont les seules a pouvoir avoir un `vote_session_id` renseigne.
### `voting_protocols`
Protocoles de vote reutilisables.
| Colonne | Type | Description |
| ------------------ | ------------ | ----------------------------------------------- |
| id | UUID (PK) | Identifiant unique |
| name | VARCHAR(128) | Nom du protocole |
| description | TEXT | Description |
| vote_type | VARCHAR(32) | Type de vote : binary, nuanced |
| formula_config_id | UUID (FK) | -> formula_configs.id |
| mode_params | VARCHAR(64) | Parametres compacts ("D30M50B.1G.2T.1") |
| is_meta_governed | BOOLEAN | Le protocole est-il sous meta-gouvernance |
| created_at | TIMESTAMPTZ | Date de creation |
### `formula_configs`
Configurations de formules de seuil WoT.
| Colonne | Type | Description |
| ------------------------- | ------------ | ----------------------------------------- |
| id | UUID (PK) | Identifiant unique |
| name | VARCHAR(128) | Nom de la configuration |
| description | TEXT | Description |
| duration_days | INTEGER | Duree du vote en jours |
| majority_pct | INTEGER | Pourcentage de majorite (0-100) |
| base_exponent | FLOAT | Exposant de base B |
| gradient_exponent | FLOAT | Exposant de gradient G |
| constant_base | FLOAT | Base constante C |
| smith_exponent | FLOAT | Exposant Smith S (null si non requis) |
| techcomm_exponent | FLOAT | Exposant TechComm T (null si non requis) |
| nuanced_min_participants | INTEGER | Participants minimum (vote nuance) |
| nuanced_threshold_pct | INTEGER | Seuil positif % (vote nuance) |
| created_at | TIMESTAMPTZ | Date de creation |
### `sanctuary_entries`
Entrees du sanctuaire (archivage immuable). Le champ `reference_id` permet de retrouver toutes les entrees liees a un document, une decision ou une session de vote via l'endpoint `/by-reference/{reference_id}` (Sprint 2). L'endpoint `/verify` recalcule le hash et verifie la coherence IPFS/on-chain.
| Colonne | Type | Description |
| -------------- | ------------ | ------------------------------------------ |
| id | UUID (PK) | Identifiant unique |
| entry_type | VARCHAR(64) | Type : document, decision, vote_result |
| reference_id | UUID | UUID de l'entite source (indexe pour recherche par reference) |
| title | VARCHAR(256) | Titre |
| content_hash | VARCHAR(128) | Hash SHA-256 du contenu |
| ipfs_cid | VARCHAR(128) | CID IPFS (renseigne lors de l'upload) |
| chain_tx_hash | VARCHAR(128) | Hash de la transaction on-chain |
| chain_block | INTEGER | Numero de bloc de la transaction |
| metadata_json | TEXT | Metadonnees JSON supplementaires |
| created_at | TIMESTAMPTZ | Date de creation |
### `blockchain_cache`
Cache des donnees blockchain pour eviter les appels RPC repetes.
| Colonne | Type | Description |
| ------------ | ------------ | -------------------------------- |
| id | UUID (PK) | Identifiant unique |
| cache_key | VARCHAR(256) | Cle de cache (unique, indexee) |
| cache_value | JSONB | Valeur en cache |
| fetched_at | TIMESTAMPTZ | Date de recuperation |
| expires_at | TIMESTAMPTZ | Date d'expiration du cache |
## Diagramme des relations
```
duniter_identities
|-- 1:N --> sessions
|-- 1:N --> votes (voter_id)
|-- 1:N --> item_versions (proposed_by_id)
|-- 1:N --> decisions (created_by_id)
|-- 1:N --> mandates (mandatee_id)
documents
|-- 1:N --> document_items
|-- 1:N ..> sanctuary_entries (via reference_id, non FK)
document_items
|-- 1:N --> item_versions (cascade delete)
|-- N:1 --> voting_protocols
item_versions
|-- N:1 --> decisions
decisions
|-- 1:N --> decision_steps
|-- 1:N ..> sanctuary_entries (via reference_id, non FK)
decision_steps
|-- N:1 --> vote_sessions
vote_sessions
|-- 1:N --> votes
|-- N:1 --> voting_protocols
|-- 1:N ..> sanctuary_entries (via reference_id, non FK)
mandates
|-- 1:N --> mandate_steps
|-- N:1 --> decisions
mandate_steps
|-- N:1 --> vote_sessions
voting_protocols
|-- N:1 --> formula_configs
formula_configs
|-- 1:N --> voting_protocols
sanctuary_entries
|-- reference_id ..> documents | decisions | vote_sessions (lien logique)
```
> **Note Sprint 2** : Les liens `..>` (pointilles) representent des references logiques via le champ `reference_id` de `sanctuary_entries`. Ce ne sont pas des cles etrangeres PostgreSQL car `reference_id` peut pointer vers differentes tables selon le `entry_type`.
+284
View File
@@ -0,0 +1,284 @@
---
title: Formules
description: Formules mathematiques de seuil WoT, criteres Smith et TechComm, simulation et meta-gouvernance
---
# Formules de seuil
libreDecision utilise un systeme de formules mathematiques pour determiner les seuils d'adoption des votes. Le mecanisme central est la **formule d'inertie WoT** qui impose une quasi-unanimite en cas de faible participation et converge vers une majorite simple a participation elevee.
## Formule principale -- Seuil WoT
$$
\text{Result} = C + B^W + \left( M + (1 - M) \cdot \left(1 - \left(\frac{T}{W}\right)^G \right) \right) \cdot \max(0,\; T - C)
$$
### Variables et plages de valeurs
| Symbole | Parametre | Description | Plage | Defaut |
| ------- | ------------------- | ------------------------------------------------ | ------------------- | ------ |
| $C$ | `constant_base` | Base constante additive (plancher minimum de votes favorables requis) | $[0, +\infty[$ | 0.0 |
| $B$ | `base_exponent` | Exposant de base. $B^W$ ajoute un terme negligeable quand $W$ est grand | $]0, 1[$ | 0.1 |
| $W$ | `wot_size` | Taille du corpus des votants eligibles (membres WoT) | $[1, +\infty[$ | -- |
| $T$ | `total_votes` | Nombre total de votes exprimes (pour + contre) | $[0, W]$ | -- |
| $M$ | `majority_pct / 100`| Ratio de majorite cible a pleine participation | $]0, 1]$ | 0.50 |
| $G$ | `gradient_exponent` | Controle la vitesse de convergence de la super-majorite vers $M$ | $]0, +\infty[$ | 0.2 |
#### Detail des parametres
**$C$ -- Base constante** : Nombre minimum absolu de votes favorables requis, independamment de la participation. Avec $C = 3$, il faut au moins 3 votes favorables meme si la formule calcule un seuil inferieur. En general, $C = 0$.
**$B$ -- Exposant de base** : Genere un terme $B^W$ qui decroit exponentiellement avec la taille de la WoT. Pour $B = 0.1$ et $W = 7224$, $B^W = 0.1^{7224} \approx 0$. Ce terme n'est pertinent que pour de tres petites communautes. Plus $B$ est proche de 0, plus le terme decroit vite. Plus $B$ est proche de 1, plus il reste significatif.
**$M$ -- Ratio de majorite** : Determine la proportion de votes favorables necessaire quand toute la WoT vote. $M = 0.5$ signifie qu'a pleine participation, la majorite simple (50%+1) suffit. $M = 0.66$ impose une super-majorite des 2/3 meme a pleine participation.
**$G$ -- Gradient** : Controle la courbure de la transition entre quasi-unanimite (faible participation) et majorite $M$ (pleine participation). Plus $G$ est petit, plus la transition est rapide : l'inertie chute vite des les premieres participations. Plus $G$ est grand, plus l'exigence de quasi-unanimite persiste a des taux de participation eleves.
### Mecanisme d'inertie
Le coeur de la formule est le facteur d'inertie :
$$
\text{inertia}(T, W) = M + (1 - M) \cdot \left(1 - \left(\frac{T}{W}\right)^G \right)
$$
- Quand la **participation est faible** ($T \ll W$) : le ratio $T/W$ est petit, $(T/W)^G$ est proche de 0, donc l'inertie tend vers $M + (1-M) = 1$. Il faut quasiment l'unanimite.
- Quand la **participation est elevee** ($T \to W$) : le ratio $T/W$ tend vers 1, $(T/W)^G$ tend vers 1, donc l'inertie tend vers $M$. La majorite simple suffit.
Le facteur d'inertie represente la **proportion de votes favorables necessaire** parmi les votants effectifs. Il multiplie ensuite $(T - C)$ pour obtenir le seuil absolu.
### Table d'inertie (WoT = 7224, M = 0.50, G = 0.2)
Le tableau suivant montre comment le facteur d'inertie evolue en fonction de la participation, pour les parametres de reference `M50 G.2` avec une WoT de 7224 membres :
| Participation $T$ | Ratio $T/W$ | $(T/W)^G$ | Inertie | Seuil (votes favorables requis) | % favorables requis |
| -----------------: | ----------: | ---------: | ------: | ------------------------------: | ------------------: |
| 10 | 0.0014 | 0.2512 | 0.8744 | 9 | 87.4% |
| 50 | 0.0069 | 0.3548 | 0.8226 | 42 | 82.3% |
| 100 | 0.0138 | 0.3981 | 0.8010 | 81 | 80.1% |
| 120 | 0.0166 | 0.4102 | 0.7949 | 96 | 79.5% |
| 200 | 0.0277 | 0.4467 | 0.7766 | 156 | 77.7% |
| 500 | 0.0692 | 0.5129 | 0.7435 | 372 | 74.4% |
| 1000 | 0.1384 | 0.5754 | 0.7123 | 713 | 71.2% |
| 2000 | 0.2769 | 0.6452 | 0.6774 | 1355 | 67.7% |
| 3612 | 0.5000 | 0.7071 | 0.6464 | 2335 | 64.6% |
| 5000 | 0.6920 | 0.7490 | 0.6255 | 3128 | 62.6% |
| 7224 | 1.0000 | 1.0000 | 0.5000 | 3612 | 50.0% |
On constate que meme a 500 votants (6.9% de participation), il faut encore 74.4% de votes favorables. L'inertie ne descend en dessous de 66% qu'au-dela de 2000 votants (27.7% de participation).
### Decomposition pas a pas
Pour le vote de l'Engagement Forgeron v2.0.0 avec $W = 7224$, $T = 120$, $M = 0.50$, $B = 0.1$, $G = 0.2$, $C = 0$ :
$$
\begin{aligned}
B^W &= 0.1^{7224} \approx 0 \\[6pt]
\frac{T}{W} &= \frac{120}{7224} \approx 0.0166 \\[6pt]
\left(\frac{T}{W}\right)^G &= 0.0166^{0.2} \approx 0.4102 \\[6pt]
1 - \left(\frac{T}{W}\right)^G &\approx 0.5898 \\[6pt]
\text{inertia} &= 0.50 + 0.50 \times 0.5898 = 0.7949 \\[6pt]
\text{Result} &= 0 + 0 + 0.7949 \times \max(0, 120 - 0) \\[3pt]
&= 0.7949 \times 120 \approx 95.39 \\[3pt]
&\Rightarrow \text{Seuil} = 94 \quad \text{(arrondi inferieur)}
\end{aligned}
$$
Resultat : **adopte** (97 >= 94).
## Critere Smith (Forgerons)
$$
\text{SmithThreshold} = \lceil \text{SmithWotSize}^S \rceil
$$
Le critere Smith exige un nombre minimum de votes favorables de la part des membres Smith (forgerons) pour que certaines decisions soient valides.
| Symbole | Parametre | Description | Plage | Defaut |
| ------- | ---------------- | ---------------------------- | ---------------- | ------------------ |
| $S$ | `smith_exponent` | Exposant pour le critere Smith | $]0, 1]$ | null (desactive) |
L'exposant $S$ controle l'exigence en fonction de la taille du groupe Smith. Plus $S$ est petit, moins de forgerons doivent voter favorablement. Plus $S$ est grand (proche de 1), plus le seuil se rapproche de la taille totale du groupe.
| SmithWotSize | $S = 0.1$ | $S = 0.3$ | $S = 0.5$ | $S = 0.7$ |
| -----------: | --------: | --------: | --------: | --------: |
| 5 | 2 | 2 | 3 | 4 |
| 10 | 2 | 2 | 4 | 6 |
| 20 | 2 | 3 | 5 | 10 |
| 50 | 2 | 4 | 8 | 19 |
| 100 | 2 | 4 | 10 | 26 |
Avec un exposant de $S = 0.1$ et 20 forgerons :
$$
\lceil 20^{0.1} \rceil = \lceil 1.35 \rceil = 2
$$
Au minimum 2 votes favorables de forgerons sont requis.
## Critere TechComm (Comite Technique)
$$
\text{TechCommThreshold} = \lceil \text{CoTecSize}^{T_c} \rceil
$$
Le critere TechComm fonctionne de maniere identique au critere Smith mais pour les membres du Comite Technique.
| Symbole | Parametre | Description | Plage | Defaut |
| ------- | ------------------- | ------------------------------- | -------------- | ------------------ |
| $T_c$ | `techcomm_exponent` | Exposant pour le critere TechComm | $]0, 1]$ | null (desactive) |
Avec un exposant de $T_c = 0.1$ et 5 membres TechComm :
$$
\lceil 5^{0.1} \rceil = \lceil 1.17 \rceil = 2
$$
Au minimum 2 votes favorables de membres TechComm sont requis.
## Resultat final
Un vote est **adopte** si et seulement si les trois conditions sont remplies simultanement :
1. `votes_for >= seuil_WoT` (formule principale)
2. `smith_votes_for >= seuil_Smith` (si critere Smith actif)
3. `techcomm_votes_for >= seuil_TechComm` (si critere TechComm actif)
En notation formelle :
$$
\text{Adopte} = \left( V_{\text{pour}} \geq \text{Seuil}_{\text{WoT}} \right) \;\wedge\; \left( S_{\text{actif}} \Rightarrow V_{\text{smith}} \geq \lceil W_S^S \rceil \right) \;\wedge\; \left( T_{\text{actif}} \Rightarrow V_{\text{tech}} \geq \lceil W_T^{T_c} \rceil \right)
$$
## Parametres de mode (mode_params)
Les parametres de formule sont encodes dans une chaine compacte pour faciliter la lecture et le partage. Format : une lettre majuscule suivie d'une valeur numerique.
| Code | Parametre | Type | Plage | Exemple |
| ---- | --------------------- | ----- | -------------- | ------------ |
| D | `duration_days` | int | $[1, 365]$ | D30 = 30 jours |
| M | `majority_pct` | int | $[1, 100]$ | M50 = 50% |
| B | `base_exponent` | float | $]0, 1[$ | B.1 = 0.1 |
| G | `gradient_exponent` | float | $]0, +\infty[$| G.2 = 0.2 |
| C | `constant_base` | float | $[0, +\infty[$| C0 = 0.0 |
| S | `smith_exponent` | float | $]0, 1]$ | S.1 = 0.1 |
| T | `techcomm_exponent` | float | $]0, 1]$ | T.1 = 0.1 |
| N | `ratio_multiplier` | float | $]0, +\infty[$| N1.5 = 1.5 |
| R | `is_ratio_mode` | bool | $\{0, 1\}$ | R1 = true |
### Exemples
- `"D30M50B.1G.2"` -- 30 jours, majorite 50%, base 0.1, gradient 0.2
- `"D30M50B.1G.2S.1T.1"` -- Idem avec critere Smith (0.1) et TechComm (0.1)
- `"D60M66B.05G.3"` -- 60 jours, majorite 66%, base 0.05, gradient 0.3
## Vote nuance
En plus du vote binaire (pour/contre), libreDecision supporte un vote nuance a 6 niveaux :
| Niveau | Label | Valeur normalisee |
| ------ | ------------- | ----------------: |
| 0 | CONTRE | 0.0 |
| 1 | PAS DU TOUT | 0.2 |
| 2 | PAS D'ACCORD | 0.4 |
| 3 | NEUTRE | 0.6 |
| 4 | D'ACCORD | 0.8 |
| 5 | TOUT A FAIT | 1.0 |
### Regle d'adoption (vote nuance)
Un vote nuance est adopte si :
1. Le nombre de votes aux niveaux 3, 4 et 5 (positifs) represente au moins `threshold_pct`% du total des votes.
2. Le nombre minimum de participants (`min_participants`) est atteint.
En notation formelle :
$$
\text{Adopte}_{\text{nuance}} = \left( \frac{V_3 + V_4 + V_5}{T} \geq \frac{\text{threshold\_pct}}{100} \right) \;\wedge\; \left( T \geq \text{min\_participants} \right)
$$
Par defaut : `threshold_pct = 80%`, `min_participants = 59`.
## Simulateur de formules
L'API expose un endpoint de simulation qui permet de tester le comportement de la formule sans creer de session de vote. Cela permet aux utilisateurs d'experimenter differentes configurations avant de les proposer.
### Utilisation de l'API de simulation
**Endpoint** : `POST /api/v1/protocols/simulate`
**Exemple de requete** :
```bash
curl -X POST https://libredecision.example.org/api/v1/protocols/simulate \
-H "Content-Type: application/json" \
-d '{
"wot_size": 7224,
"total_votes": 120,
"majority_pct": 50,
"base_exponent": 0.1,
"gradient_exponent": 0.2,
"constant_base": 0.0,
"votes_for": 97
}'
```
**Exemple de reponse** :
```json
{
"threshold": 94,
"inertia_factor": 0.7949,
"participation_ratio": 0.0166,
"base_term": 0.0,
"smith_threshold": null,
"techcomm_threshold": null,
"adopted": true,
"details": {
"wot_threshold_met": true,
"smith_met": null,
"techcomm_met": null
}
}
```
### Cas d'usage du simulateur
1. **Calibration** : Tester differentes valeurs de $M$ et $G$ pour trouver un equilibre entre exigence et praticabilite.
2. **Comparaison** : Comparer les seuils entre deux configurations sur les memes donnees de participation.
3. **Prediction** : Estimer le seuil pour un vote a venir en fonction de la participation attendue.
4. **Pedagogie** : Comprendre visuellement l'impact de chaque parametre sur le seuil.
## Meta-gouvernance
La meta-gouvernance est le mecanisme par lequel les **regles du vote elles-memes** sont soumises au vote. Concretement, modifier un protocole de vote ou une formule de seuil active est un acte de gouvernance qui necessite une validation collective.
### Principe
Quand un utilisateur propose une modification d'un protocole ou d'une formule en cours d'utilisation (lies a au moins une session ouverte), le systeme :
1. **Bloque la modification directe** (reponse HTTP 409).
2. **Cree automatiquement une session de vote** portant sur la modification proposee.
3. **Applique le protocole de vote en vigueur** pour valider ou rejeter la modification.
4. Si le vote est **adopte**, la modification est appliquee aux futures sessions de vote. Les sessions en cours ne sont pas affectees.
5. Si le vote est **rejete**, la modification est archivee et le protocole reste inchange.
### Quels objets sont proteges ?
| Objet | Endpoint de modification | Meta-gouvernance |
| ----------------- | -------------------------- | ---------------- |
| Protocole actif | `PUT /api/v1/protocols/{id}` | Oui |
| Formule active | `PUT /api/v1/protocols/formulas/{id}` | Oui |
| Protocole inactif | `PUT /api/v1/protocols/{id}` | Non (modification directe) |
| Formule inactive | `PUT /api/v1/protocols/formulas/{id}` | Non (modification directe) |
Un protocole ou une formule est considere **actif** s'il est lie a au moins une session de vote au statut `open`.
### Cas d'usage
- **Changer la duree de vote** : Un membre propose de passer de 30 a 60 jours. La proposition est soumise au vote avec les regles actuelles. Si adoptee, les prochains votes dureront 60 jours.
- **Modifier le gradient** : Un membre estime que le gradient $G = 0.2$ est trop permissif et propose $G = 0.4$. Le vote decide.
- **Ajouter un critere Smith** : Un membre propose d'activer le critere Smith avec $S = 0.1$ pour impliquer les forgerons. Le vote decide.
La meta-gouvernance garantit qu'aucun parametre fondamental du systeme de decision ne peut etre modifie unilateralement.
@@ -0,0 +1,165 @@
---
title: Integration blockchain
description: Integration Duniter V2, IPFS et ancrage on-chain
---
# Integration blockchain
libreDecision s'integre a la blockchain Duniter V2 pour trois fonctions essentielles :
1. **Authentification** -- Verification de l'identite des membres via signature Ed25519
2. **Donnees WoT** -- Recuperation des tailles WoT, Smith et TechComm pour le calcul des seuils
3. **Ancrage on-chain** -- Archivage immuable des resultats via `system.remark`
## Duniter V2 RPC
La communication avec le noeud Duniter V2 utilise la bibliotheque `substrate-interface` via WebSocket RPC.
### Configuration
```
DUNITER_RPC_URL=wss://gdev.p2p.legal/ws
```
### Requetes principales
#### Taille de la WoT (membres)
```python
from substrateinterface import SubstrateInterface
substrate = SubstrateInterface(url="wss://gdev.p2p.legal/ws")
result = substrate.query(
module="Membership",
storage_function="MembershipCount",
)
wot_size = int(result.value)
```
#### Taille Smith (forgerons)
```python
result = substrate.query(
module="SmithMembers",
storage_function="SmithMembershipCount",
)
smith_size = int(result.value)
```
#### Taille TechComm
```python
result = substrate.query(
module="TechnicalCommittee",
storage_function="Members",
)
techcomm_size = len(result.value) if result.value else 0
```
### Cache blockchain
Pour eviter des appels RPC repetes, les donnees blockchain sont mises en cache dans la table `blockchain_cache` avec une duree d'expiration configurable. La cle de cache est une chaine descriptive (ex: `"wot_size"`, `"smith_size"`), la valeur est stockee en JSONB.
## IPFS (kubo)
Le composant IPFS est un noeud kubo qui sert de stockage distribue pour le Sanctuaire. Chaque document adopte, resultat de vote ou decision finalisee est uploade sur IPFS.
### Configuration
```
IPFS_API_URL=http://localhost:5001
IPFS_GATEWAY_URL=http://localhost:8080
```
### Upload de contenu
```python
import httpx
async with httpx.AsyncClient() as client:
response = await client.post(
f"{IPFS_API_URL}/api/v0/add",
files={"file": ("content.txt", content.encode("utf-8"))},
)
response.raise_for_status()
ipfs_cid = response.json()["Hash"]
```
### Acces au contenu
Le contenu est accessible via la passerelle IPFS :
```
GET http://localhost:8080/ipfs/{cid}
```
## Ancrage on-chain (system.remark)
L'ancrage on-chain consiste a soumettre un extrinsic `system.remark` contenant le hash SHA-256 du contenu archive. Cela cree une preuve immuable et horodatee sur la blockchain Duniter V2.
### Format du remark
```
libredecision:sanctuary:{content_hash_sha256}
```
### Soumission
```python
from substrateinterface import SubstrateInterface, Keypair
substrate = SubstrateInterface(url="wss://gdev.p2p.legal/ws")
call = substrate.compose_call(
call_module="System",
call_function="remark",
call_params={"remark": f"libredecision:sanctuary:{content_hash}"},
)
extrinsic = substrate.create_signed_extrinsic(call=call, keypair=keypair)
receipt = substrate.submit_extrinsic(extrinsic, wait_for_inclusion=True)
tx_hash = receipt.extrinsic_hash
block_number = receipt.block_number
```
### Verification
Pour verifier qu'un contenu a ete ancre, il suffit de :
1. Recalculer le hash SHA-256 du contenu
2. Rechercher le remark correspondant dans la blockchain
3. Verifier que le hash correspond
## Flux complet du Sanctuaire
```
Contenu adopte
|
v
[SHA-256] --> content_hash
|
+---> [IPFS /api/v0/add] --> ipfs_cid
|
+---> [system.remark] --> tx_hash, block_number
|
v
[sanctuary_entries] -- Enregistrement en base avec content_hash, ipfs_cid, chain_tx_hash, chain_block
```
## Authentification Ed25519
Le flux d'authentification utilise un mecanisme challenge-response :
1. Le serveur genere un challenge aleatoire (64 caracteres hexadecimaux)
2. Le client signe le challenge avec sa cle privee Ed25519 (Duniter V2)
3. Le serveur verifie la signature a l'aide de la cle publique derivee de l'adresse SS58
```python
from substrateinterface import Keypair
keypair = Keypair(ss58_address=address)
is_valid = keypair.verify(challenge_bytes, signature_bytes)
```
Cette methode garantit que seul le proprietaire de l'adresse Duniter peut s'authentifier, sans jamais transmettre la cle privee.
+145
View File
@@ -0,0 +1,145 @@
---
title: Contribution
description: Guide de contribution au projet libreDecision
---
# Guide de contribution
Merci de votre interet pour contribuer a libreDecision. Ce guide explique comment configurer l'environnement de developpement, les conventions a respecter et le processus de contribution.
## Prerequis
- Python 3.11+
- Node.js 20+
- PostgreSQL 16
- Docker et Docker Compose (optionnel mais recommande)
- Git
## Installation locale
### Methode 1 : Docker (recommandee)
```bash
# Cloner le depot
git clone https://git.duniter.org/tools/libredecision.git
cd libredecision
# Copier le fichier d'environnement
cp .env.example .env
# Demarrer tous les services
docker compose -f docker/docker-compose.yml -f docker/docker-compose.dev.yml up
```
Les services sont accessibles sur :
- Frontend : http://localhost:3002
- Backend : http://localhost:8002
- API docs : http://localhost:8002/docs
### Methode 2 : Installation manuelle
```bash
# Backend
cd backend
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
uvicorn app.main:app --port 8002 --reload
# Frontend (dans un autre terminal)
cd frontend
npm install
npm run dev
```
Assurez-vous qu'une instance PostgreSQL est disponible et que `DATABASE_URL` dans `.env` pointe vers celle-ci.
## Conventions
### Langues
- **Code** (variables, commentaires, docstrings) : anglais
- **Interface utilisateur** (labels, messages, documentation) : francais
### Structure du code
Le backend est organise par domaine :
```
backend/app/
models/ # Modeles SQLAlchemy (un fichier par domaine)
schemas/ # Schemas Pydantic v2 (un fichier par domaine)
routers/ # Routes FastAPI (un fichier par domaine)
services/ # Logique metier (un fichier par domaine)
engine/ # Moteur de calcul (formules, seuils)
tests/ # Tests unitaires
```
Le frontend suit les conventions Nuxt 4 :
```
frontend/app/
components/ # Composants Vue (un dossier par domaine)
composables/ # Composables reutilisables
pages/ # Pages (un dossier par domaine)
stores/ # Stores Pinia
utils/ # Utilitaires
```
### Style de code
- **Python** : PEP 8, type hints systematiques, docstrings au format NumPy
- **TypeScript/Vue** : ESLint + Prettier (via configuration Nuxt)
- **SQL** : noms de tables au pluriel, noms de colonnes en snake_case
### API
- Versionne sous `/api/v1/`
- Schemas Pydantic v2 pour la validation
- Async partout (SQLAlchemy AsyncSession, FastAPI async handlers)
- Codes HTTP standards (201 pour creation, 204 pour suppression, 404, 409, etc.)
## Tests
### Backend
```bash
cd backend
pytest app/tests/ -v
```
Les tests du moteur de calcul (`test_threshold.py`) verifient la formule de seuil WoT avec le cas de reference (Engagement Forgeron v2.0.0 : 97/23 avec WoT 7224).
### Frontend
```bash
cd frontend
npm run build # Verification que le build passe
```
## Processus de contribution
1. Creer une branche a partir de `main` : `git checkout -b feature/ma-fonctionnalite`
2. Developper et tester localement
3. S'assurer que les tests passent : `pytest` (backend) et `npm run build` (frontend)
4. Pousser la branche et creer une merge request
5. La pipeline CI (Woodpecker) validera automatiquement les tests
6. Revue de code par un mainteneur
7. Merge dans `main`
## Secrets Woodpecker CI
La pipeline CI utilise les secrets suivants (a configurer dans l'interface Woodpecker) :
| Secret | Description |
| ----------------- | ------------------------------------ |
| `docker_registry` | URL du registre Docker |
| `docker_username` | Nom d'utilisateur du registre |
| `docker_password` | Mot de passe du registre |
| `deploy_host` | Adresse du serveur de deploiement |
| `deploy_username` | Utilisateur SSH du serveur |
| `deploy_key` | Cle privee SSH pour le deploiement |
## Contact
Pour toute question, rendez-vous sur le forum Duniter ou ouvrez une issue sur le depot Git.
+405
View File
@@ -0,0 +1,405 @@
---
title: Deploiement
description: Guide de deploiement en production de libreDecision
---
# Deploiement
Ce guide couvre le deploiement complet de libreDecision en production avec Docker, Traefik, PostgreSQL et IPFS.
## Prerequis
| Composant | Version minimale | Description |
| --------- | ---------------- | ----------- |
| Docker | 24+ | Moteur de conteneurs |
| Docker Compose | 2.20+ | Orchestration multi-conteneurs |
| Nom de domaine | -- | Domaine pointe vers le serveur (ex: `decision.librodrome.org`) |
| Certificat TLS | -- | Genere automatiquement par Traefik via Let's Encrypt |
| Traefik | 2.10+ | Reverse proxy avec terminaison TLS (reseau externe `traefik`) |
| Serveur | 2 vCPU, 4 Go RAM, 40 Go SSD | Ressources recommandees |
### Reseau Traefik
Le deploiement suppose qu'un reseau Docker externe `traefik` existe deja avec une instance Traefik configuree. Si ce n'est pas le cas, creez-le :
```bash
docker network create traefik
```
Et deployez Traefik separement (voir section [Configuration Traefik](#configuration-traefik)).
## Configuration .env
Copiez le fichier `.env.example` et configurez chaque variable pour la production :
```bash
cp .env.example .env
```
### Variables d'environnement
| Variable | Description | Valeur par defaut | Production |
| -------- | ----------- | ----------------- | ---------- |
| `POSTGRES_DB` | Nom de la base de donnees | `libredecision` | `libredecision` |
| `POSTGRES_USER` | Utilisateur PostgreSQL | `libredecision` | `libredecision` |
| `POSTGRES_PASSWORD` | Mot de passe PostgreSQL | `change-me-in-production` | **Generer un mot de passe fort** (32+ caracteres) |
| `DATABASE_URL` | URL de connexion asyncpg | `postgresql+asyncpg://...@localhost:5432/...` | Construite automatiquement dans docker-compose |
| `SECRET_KEY` | Cle secrete pour les tokens de session | `change-me-in-production-with-a-real-secret-key` | **Generer une cle aleatoire** (`openssl rand -hex 64`) |
| `DEBUG` | Mode debug | `true` | **`false`** |
| `CORS_ORIGINS` | Origines CORS autorisees | `["http://localhost:3002"]` | `["https://decision.librodrome.org"]` |
| `DUNITER_RPC_URL` | URL du noeud Duniter V2 RPC | `wss://gdev.p2p.legal/ws` | URL du noeud de production |
| `IPFS_API_URL` | URL de l'API IPFS (kubo) | `http://localhost:5001` | `http://ipfs:5001` (interne Docker) |
| `IPFS_GATEWAY_URL` | URL de la passerelle IPFS | `http://localhost:8080` | `http://ipfs:8080` (interne Docker) |
| `NUXT_PUBLIC_API_BASE` | URL publique de l'API pour le frontend | `http://localhost:8002/api/v1` | `https://decision.librodrome.org/api/v1` |
| `DOMAIN` | Nom de domaine | `decision.librodrome.org` | Votre domaine |
### Generer les secrets
```bash
# Generer POSTGRES_PASSWORD
openssl rand -hex 32
# Generer SECRET_KEY
openssl rand -hex 64
```
::callout{type="warning"}
Ne commitez jamais le fichier `.env` contenant les secrets de production. Ajoutez-le au `.gitignore`.
::
## Lancement avec docker-compose
### Premier deploiement
```bash
# Se placer dans le repertoire du projet
cd /opt/libredecision
# Cloner le depot
git clone https://git.duniter.org/tools/libredecision.git .
# Configurer l'environnement
cp .env.example .env
# Editer .env avec les valeurs de production
# Construire et demarrer les services
docker compose -f docker/docker-compose.yml up -d --build
# Verifier que tous les services sont sains
docker compose -f docker/docker-compose.yml ps
```
### Services deployes
| Service | Description | Port interne | Expose |
| ------- | ----------- | ------------ | ------ |
| `postgres` | Base de donnees PostgreSQL 16 | 5432 | Non (interne uniquement) |
| `backend` | API FastAPI | 8002 | Via Traefik (`/api/*`) |
| `frontend` | Application Nuxt 4 | 3000 | Via Traefik (racine) |
| `ipfs` | Noeud IPFS kubo | 5001, 8080 | Non (interne uniquement) |
### Verifier le deploiement
```bash
# Statut des conteneurs
docker compose -f docker/docker-compose.yml ps
# Logs d'un service specifique
docker compose -f docker/docker-compose.yml logs -f backend
# Health check de l'API
curl -s https://decision.librodrome.org/api/health | jq .
```
## Migration de base de donnees (Alembic)
libreDecision utilise Alembic pour les migrations de schema PostgreSQL.
### Appliquer les migrations
```bash
# Executer les migrations dans le conteneur backend
docker compose -f docker/docker-compose.yml exec backend alembic upgrade head
```
### Verifier l'etat des migrations
```bash
docker compose -f docker/docker-compose.yml exec backend alembic current
```
### Creer une nouvelle migration
```bash
# En developpement uniquement
docker compose -f docker/docker-compose.yml exec backend alembic revision --autogenerate -m "description de la migration"
```
### Rollback d'une migration
```bash
# Revenir d'une migration
docker compose -f docker/docker-compose.yml exec backend alembic downgrade -1
```
## Seed des donnees initiales
Apres la premiere migration, vous pouvez charger les donnees de seed (documents de reference initiaux) :
```bash
# Executer le script de seed dans le conteneur backend
docker compose -f docker/docker-compose.yml exec backend python -m app.seed
```
Les donnees de seed incluent :
- La Licence G1 avec ses items
- L'Engagement Forgeron v2.0.0
- L'Engagement Comite Technique v2.0.0
- Le template de processus de Runtime Upgrade
- Les protocoles de vote par defaut avec leurs formules
## Configuration Traefik
### Configuration minimale Traefik
Si vous n'avez pas encore Traefik, voici une configuration minimale. Creez un fichier `docker-compose.traefik.yml` :
```yaml
version: "3.9"
services:
traefik:
image: traefik:v2.10
restart: unless-stopped
command:
- "--api.dashboard=false"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--providers.docker.network=traefik"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--entrypoints.web.http.redirections.entryPoint.to=websecure"
- "--entrypoints.web.http.redirections.entryPoint.scheme=https"
- "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
- "--certificatesresolvers.letsencrypt.acme.email=admin@decision.librodrome.org"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
ports:
- "80:80"
- "443:443"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
- "letsencrypt:/letsencrypt"
networks:
- traefik
volumes:
letsencrypt:
networks:
traefik:
external: true
```
```bash
docker compose -f docker-compose.traefik.yml up -d
```
### Routage
Le `docker-compose.yml` de libreDecision configure automatiquement les labels Traefik :
- **Frontend** : `Host(decision.librodrome.org)` sur le port 3000
- **Backend** : `Host(decision.librodrome.org) && PathPrefix(/api)` sur le port 8002
- Les deux utilisent le certificat Let's Encrypt (`certresolver=letsencrypt`)
- Redirection HTTP vers HTTPS automatique
### Certificat TLS
Le certificat est genere automatiquement par Traefik via Let's Encrypt (challenge TLS-ALPN-01). Assurez-vous que :
1. Le domaine pointe vers l'adresse IP du serveur (enregistrement DNS A)
2. Les ports 80 et 443 sont ouverts dans le firewall
3. L'adresse email dans la configuration Traefik est valide
## Monitoring
### Health checks
Le service PostgreSQL dispose d'un health check integre (`pg_isready`). Le backend expose un endpoint de sante :
```bash
# Health check de l'API
curl -s https://decision.librodrome.org/api/health
# Reponse attendue : {"status": "healthy"}
```
### Logs
```bash
# Suivre les logs de tous les services
docker compose -f docker/docker-compose.yml logs -f
# Logs d'un service specifique
docker compose -f docker/docker-compose.yml logs -f backend
docker compose -f docker/docker-compose.yml logs -f frontend
docker compose -f docker/docker-compose.yml logs -f postgres
# Derniers 100 lignes
docker compose -f docker/docker-compose.yml logs --tail=100 backend
```
### Metriques
Surveillez les indicateurs suivants :
| Indicateur | Commande | Seuil d'alerte |
| ---------- | -------- | --------------- |
| CPU/RAM conteneurs | `docker stats` | > 80% RAM |
| Espace disque | `df -h` | > 85% |
| Connexions PostgreSQL | `docker exec postgres psql -U libredecision -c "SELECT count(*) FROM pg_stat_activity;"` | > 80 |
| Taille base de donnees | `docker exec postgres psql -U libredecision -c "SELECT pg_size_pretty(pg_database_size('libredecision'));"` | Information |
| Statut IPFS | `docker exec ipfs ipfs id` | Erreur |
## Sauvegarde PostgreSQL
### Sauvegarde manuelle
```bash
# Dump complet de la base
docker compose -f docker/docker-compose.yml exec postgres \
pg_dump -U libredecision -Fc libredecision > backup_$(date +%Y%m%d_%H%M%S).dump
```
### Restauration
```bash
# Restaurer un dump
docker compose -f docker/docker-compose.yml exec -T postgres \
pg_restore -U libredecision -d libredecision --clean < backup_20260228_120000.dump
```
### Sauvegarde automatique (cron)
Ajoutez un crontab pour des sauvegardes quotidiennes :
```bash
# Editer le crontab
crontab -e
# Ajouter une sauvegarde quotidienne a 3h du matin
0 3 * * * cd /opt/libredecision && docker compose -f docker/docker-compose.yml exec -T postgres pg_dump -U libredecision -Fc libredecision > /opt/backups/libredecision_$(date +\%Y\%m\%d).dump && find /opt/backups -name "libredecision_*.dump" -mtime +30 -delete
```
Cette commande :
1. Execute un dump quotidien a 3h du matin
2. Stocke les sauvegardes dans `/opt/backups/`
3. Supprime automatiquement les sauvegardes de plus de 30 jours
## Mise a jour
### Procedure standard
```bash
cd /opt/libredecision
# 1. Tirer les dernieres images
docker compose -f docker/docker-compose.yml pull
# 2. Redemarrer les services avec les nouvelles images
docker compose -f docker/docker-compose.yml up -d --remove-orphans
# 3. Appliquer les migrations de base de donnees
docker compose -f docker/docker-compose.yml exec backend alembic upgrade head
# 4. Nettoyer les anciennes images
docker image prune -f
# 5. Verifier le deploiement
docker compose -f docker/docker-compose.yml ps
curl -s https://decision.librodrome.org/api/health
```
### Pipeline CI/CD (Woodpecker)
La pipeline Woodpecker CI automatise le deploiement lors d'un push sur `main` :
1. **test-backend** : execution des tests Python (`pytest`)
2. **test-frontend** : build du frontend Nuxt (`npm run build`)
3. **docker-backend** : construction et push de l'image Docker backend
4. **docker-frontend** : construction et push de l'image Docker frontend
5. **deploy** : connexion SSH au serveur, pull des nouvelles images, redemarrage
Les secrets CI sont configures dans l'interface Woodpecker :
| Secret | Description |
| ------ | ----------- |
| `docker_registry` | URL du registre Docker |
| `docker_username` | Identifiant du registre |
| `docker_password` | Mot de passe du registre |
| `deploy_host` | Adresse du serveur de production |
| `deploy_username` | Utilisateur SSH |
| `deploy_key` | Cle privee SSH |
## Troubleshooting
### Le backend ne demarre pas
**Symptome** : Le conteneur backend redemoarre en boucle.
```bash
# Verifier les logs
docker compose -f docker/docker-compose.yml logs backend
# Causes courantes :
# 1. DATABASE_URL incorrecte -> verifier .env
# 2. PostgreSQL pas encore pret -> verifier le health check
# 3. Migrations non appliquees -> executer alembic upgrade head
```
### Erreur de connexion a PostgreSQL
**Symptome** : `connection refused` ou `password authentication failed`.
```bash
# Verifier que PostgreSQL est sain
docker compose -f docker/docker-compose.yml exec postgres pg_isready
# Verifier les variables d'environnement
docker compose -f docker/docker-compose.yml exec backend env | grep DATABASE
# Reinitialiser le mot de passe si necessaire (attention : destructif)
docker compose -f docker/docker-compose.yml down -v # supprime les volumes
docker compose -f docker/docker-compose.yml up -d # recree avec le nouveau mot de passe
```
### Certificat TLS non genere
**Symptome** : Le site est inaccessible en HTTPS ou affiche un certificat invalide.
1. Verifier que le DNS A/AAAA pointe vers le serveur : `dig decision.librodrome.org`
2. Verifier que les ports 80 et 443 sont ouverts : `ss -tlnp | grep -E '80|443'`
3. Consulter les logs Traefik : `docker logs traefik 2>&1 | grep -i acme`
### IPFS ne repond pas
**Symptome** : Erreur lors de l'archivage dans le Sanctuaire.
```bash
# Verifier le statut du noeud IPFS
docker compose -f docker/docker-compose.yml exec ipfs ipfs id
# Verifier l'espace disque disponible
docker compose -f docker/docker-compose.yml exec ipfs df -h /data/ipfs
# Redemarrer le noeud IPFS
docker compose -f docker/docker-compose.yml restart ipfs
```
### Erreur WebSocket
**Symptome** : L'indicateur de connexion temps reel est rouge.
1. Verifier que Traefik supporte les WebSockets (actif par defaut)
2. Verifier le routage : le WebSocket est sur `/api/v1/ws`
3. Verifier les logs backend pour les erreurs de connexion WebSocket
+278
View File
@@ -0,0 +1,278 @@
---
title: Securite
description: Politique de securite et mesures de protection de libreDecision
---
# Securite
Ce document decrit les mesures de securite implementees dans libreDecision pour proteger l'integrite de la plateforme, des votes et des donnees des utilisateurs.
## Authentification Duniter V2 (Ed25519 challenge-response)
### Principe
libreDecision n'utilise ni mot de passe ni systeme d'inscription classique. L'authentification repose entierement sur la cryptographie Ed25519 de la blockchain Duniter V2.
### Flux challenge-response
```
Client Serveur
| |
|-- POST /auth/challenge ----------->|
| { address: "5Grw..." } |
| |-- Genere challenge (64 hex)
| |-- Stocke en memoire (TTL 5 min)
|<-------- { challenge: "a1b2..." } -|
| |
|-- Signe le challenge localement |
| (cle privee Ed25519) |
| |
|-- POST /auth/verify ------------->|
| { address, challenge, signature}|
| |-- Verifie signature Ed25519
| |-- Verifie identite WoT
| |-- Cree/retrouve DuniterIdentity
| |-- Genere token de session
|<-------- { token: "..." } --------|
```
### Garanties de securite
| Propriete | Mecanisme |
| --------- | --------- |
| **Cle privee jamais transmise** | Seule la signature est envoyee, pas la cle |
| **Anti-replay** | Chaque challenge est usage unique, expire apres 5 minutes |
| **Anti-interception** | HTTPS/TLS obligatoire en production |
| **Identite verifiee** | L'adresse SS58 est verifiee sur la blockchain via `substrate-interface` |
### Verification de la signature
```python
from substrateinterface import Keypair
keypair = Keypair(ss58_address=address)
is_valid = keypair.verify(challenge_bytes, signature_bytes)
```
La cle publique est derivee de l'adresse SS58 sans aucun appel reseau. La verification est locale et instantanee.
## Integrite des votes
### Signature cryptographique des votes
Chaque vote soumis est accompagne d'une **signature Ed25519** qui garantit son authenticite et son integrite.
### Payload signe
Le payload signe contient :
```json
{
"session_id": "uuid-de-la-session",
"vote_value": "for",
"timestamp": "2026-02-28T12:00:00Z"
}
```
Ce payload est signe par la cle privee du votant. La signature est stockee en base de donnees avec le vote, permettant une verification independante a tout moment.
### Proprietes garanties
| Propriete | Description |
| --------- | ----------- |
| **Authenticite** | Seul le proprietaire de l'adresse Duniter peut soumettre un vote en son nom |
| **Integrite** | Le payload ne peut pas etre modifie apres signature sans invalider la signature |
| **Non-repudiation** | Le votant ne peut pas nier avoir vote ; la preuve cryptographique est publique |
| **Transparence** | Les votes signes sont publics et verifiables par quiconque |
### Verification d'un vote
```python
from substrateinterface import Keypair
keypair = Keypair(ss58_address=voter_address)
is_valid = keypair.verify(signed_payload.encode(), signature_bytes)
```
### Protection contre la modification
Quand un votant modifie son vote, l'ancien vote est marque `is_active = false` mais conserve en base. Les deux votes (ancien et nouveau) conservent leur signature. L'historique complet est auditable.
## Rate limiting
### Limites par endpoint
Pour prevenir les abus et les attaques par deni de service, des limites de taux sont appliquees :
| Categorie | Endpoints | Limite | Fenetre |
| --------- | --------- | ------ | ------- |
| Authentification | `/auth/challenge`, `/auth/verify` | 10 requetes | 1 minute |
| Vote | `/votes/sessions/{id}/vote` | 5 requetes | 1 minute |
| Ecriture | `POST`, `PUT`, `DELETE` | 30 requetes | 1 minute |
| Lecture | `GET` | 200 requetes | 1 minute |
| WebSocket | `/ws` | 3 connexions | simultanees par IP |
### Reponse en cas de depassement
Le serveur retourne un code HTTP **429 Too Many Requests** avec un header `Retry-After` indiquant le temps d'attente en secondes.
## En-tetes de securite
Les en-tetes HTTP suivants sont configures en production :
| En-tete | Valeur | Description |
| ------- | ------ | ----------- |
| `Strict-Transport-Security` | `max-age=63072000; includeSubDomains; preload` | Force HTTPS pendant 2 ans, inclut les sous-domaines |
| `Content-Security-Policy` | `default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self' wss:; img-src 'self' data: https://ipfs.io` | Restreint les sources de contenu autorisees |
| `X-Content-Type-Options` | `nosniff` | Empeche le navigateur de deviner le type MIME |
| `X-Frame-Options` | `DENY` | Empeche l'inclusion dans un iframe (anti-clickjacking) |
| `X-XSS-Protection` | `1; mode=block` | Active la protection XSS du navigateur |
| `Referrer-Policy` | `strict-origin-when-cross-origin` | Limite les informations de referer |
| `Permissions-Policy` | `camera=(), microphone=(), geolocation=()` | Desactive les APIs sensibles non necessaires |
### CSP detaillee
La Content Security Policy est configuree pour :
- Autoriser les scripts uniquement depuis l'origine (`'self'`)
- Autoriser les styles inline (necessaires pour UnoCSS)
- Autoriser les connexions WebSocket (`wss:`)
- Autoriser les images depuis IPFS gateway (`https://ipfs.io`) pour l'affichage des contenus archives
- Bloquer tout le reste
## Sanctuaire : triple preuve d'integrite
### Chaine de verification
```
Contenu --> [SHA-256] --> hash
|
+---> [IPFS] --> CID (content-addressable)
|
+---> [system.remark on-chain] --> preuve horodatee
|
v
Triple preuve :
1. Hash local = hash enregistre (integrite)
2. CID IPFS = contenu identique (distribution)
3. Remark on-chain = hash confirme (immutabilite temporelle)
```
### Garanties
| Preuve | Ce qu'elle garantit | Point de defaillance |
| ------ | ------------------- | -------------------- |
| SHA-256 | Le contenu n'a pas ete modifie | Aucun (mathematique) |
| IPFS CID | Le contenu est distribue et adressable par contenu | Disponibilite des noeuds IPFS |
| On-chain remark | Le hash existait a la date du bloc | Securite de la blockchain Duniter |
### Format du remark on-chain
```
libredecision:sanctuary:{content_hash_sha256}
```
Le prefixe `libredecision:sanctuary:` permet d'identifier les ancrages de libreDecision parmi tous les remarks de la blockchain.
## WebSocket : authentification et securite
### Authentification par token
La connexion WebSocket est authentifiee via le token de session obtenu lors du challenge-response :
```
ws://server/api/v1/ws?token={session_token}
```
Le serveur verifie le token avant d'accepter la connexion. Les connexions non authentifiees sont rejetees.
### Protections WebSocket
| Protection | Description |
| ---------- | ----------- |
| Token valide requis | Pas de connexion anonyme |
| Limite de connexions | Maximum 3 connexions simultanees par IP |
| Heartbeat | Ping/pong periodique pour detecter les connexions mortes |
| Taille maximale des messages | Limite a 64 Ko pour prevenir les abus |
| Broadcast read-only | Les clients recoivent les mises a jour mais ne peuvent pas modifier l'etat via WebSocket |
## Session management
### Tokens de session
| Propriete | Valeur |
| --------- | ------ |
| Duree de vie (TTL) | 24 heures |
| Stockage serveur | Hash SHA-256 du token en base (table `sessions`) |
| Stockage client | Token en clair dans le `localStorage` du navigateur |
| Invalidation | Suppression de l'entree en base via `/auth/logout` |
### Securite des tokens
- Le token est genere avec un generateur cryptographiquement sur (CSPRNG)
- Seul le **hash** du token est stocke en base de donnees. Si la base est compromise, les tokens bruts ne sont pas exposes.
- A chaque requete authentifiee, le token fourni est hashe et compare au hash en base
- Les tokens expires sont nettoyes periodiquement
### Deconnexion
L'appel a `POST /auth/logout` invalide la session cote serveur en supprimant l'entree de la table `sessions`. Le client supprime le token de son `localStorage`.
## Audit logging
### Evenements traces
Les actions suivantes sont enregistrees pour auditabilite :
| Evenement | Donnees enregistrees |
| --------- | -------------------- |
| Authentification reussie | Adresse SS58, timestamp, IP |
| Authentification echouee | Adresse SS58, raison, timestamp, IP |
| Vote soumis | Session ID, voter ID, vote, signature, timestamp |
| Vote modifie | Session ID, voter ID, ancien vote (desactive), nouveau vote |
| Decision creee/avancee | Decision ID, auteur, action, timestamp |
| Document archive | Document ID, hash, CID, tx_hash |
| Session de vote cloturee | Session ID, resultat, seuils |
| Mandat assigne/revoque | Mandate ID, mandataire, action |
### Conservation
Les logs d'audit sont conserves de maniere permanente dans la base de donnees. Les votes et leurs signatures sont particulierement importants car ils constituent la preuve cryptographique des decisions collectives.
## Signalement de vulnerabilite (Responsible disclosure)
### Processus
Si vous decouvrez une vulnerabilite de securite dans libreDecision, merci de suivre cette procedure de divulgation responsable :
1. **Ne divulguez pas publiquement** la vulnerabilite avant qu'un correctif soit disponible.
2. **Contactez l'equipe** via le canal securise indique sur le depot Git Duniter ou via le forum Duniter (message prive aux mainteneurs).
3. **Decrivez la vulnerabilite** avec autant de details que possible :
- Type de vulnerabilite (injection, XSS, CSRF, contournement d'authentification, etc.)
- Etapes pour reproduire
- Impact potentiel
- Suggestion de correctif (si applicable)
4. **Delai de correction** : l'equipe s'engage a accuser reception sous 48 heures et a fournir un correctif sous 14 jours pour les vulnerabilites critiques.
5. **Credit** : les chercheurs en securite qui signalent des vulnerabilites de maniere responsable seront credites dans le changelog (sauf s'ils preferent l'anonymat).
### Perimetre
Le perimetre de la politique de securite couvre :
| Inclus | Exclus |
| ------ | ------ |
| API backend (FastAPI) | Infrastructure d'hebergement tiers |
| Frontend (Nuxt 4) | Blockchain Duniter V2 elle-meme |
| Authentification challenge-response | Extension Polkadot.js |
| Integrite des votes et signatures | Noeud IPFS (kubo) lui-meme |
| WebSocket | Traefik |
| Base de donnees (acces, injection) | -- |
### Exclusions
Les types de signalement suivants sont hors perimetre :
- Deni de service par volume (DDoS)
- Ingenierie sociale
- Attaques physiques sur l'infrastructure
- Vulnerabilites dans les dependances tierces deja connues et trackees (utiliser les issues du depot)