v3 : déploiement (image unique, produit à la racine, maquette en sous-domaine) et documentation du chantier
- docker/app.Dockerfile : une image Rust + Svelte, la construction rejoue toute la boucle (fmt, clippy -D warnings, tests, svelte-check, vitest) ; utilisateur non-root, volume /data. docker/app-compose.yml pour sonic (fabio). - .woodpecker/app.yml : produit sur decision.librodrome.org, attend la maquette, écarte tout conteneur qui revendique encore la racine, healthcheck du commit. - .woodpecker/maquette.yml (ex .woodpecker.yml) : la maquette v2 telle quelle, sur maquette.decision.librodrome.org, mêmes volumes. - .env.example de la maquette rangé dans maquette/ ; celui de la v3 à la racine. - Docs : README, CONTRIBUTING (travail à deux), CLAUDE.md v3, HANDOFF, docs/chantiers (feuille de route, fiche, contrôles, onboarding de Chiara, demande à poka), docs/adr 0001–0006. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
# Build context of docker/app.Dockerfile (the repository root).
|
||||
# The maquette builds from its own context (maquette/), not from here.
|
||||
.git
|
||||
.remember
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
data
|
||||
target
|
||||
maquette
|
||||
docs
|
||||
web/node_modules
|
||||
web/build
|
||||
web/.svelte-kit
|
||||
web/test-results
|
||||
web/playwright-report
|
||||
**/*.db
|
||||
**/*.db-wal
|
||||
**/*.db-shm
|
||||
+28
-43
@@ -1,49 +1,34 @@
|
||||
# PostgreSQL
|
||||
POSTGRES_DB=libredecision
|
||||
POSTGRES_USER=libredecision
|
||||
POSTGRES_PASSWORD=change-me-in-production
|
||||
DATABASE_URL=postgresql+asyncpg://libredecision:change-me-in-production@localhost:5432/libredecision
|
||||
# libreDecision v3 — variables du serveur (ld-server). Toutes facultatives en dev.
|
||||
# Le serveur lit son environnement, pas de fichier : `set -a; . ./.env; set +a` avant
|
||||
# `cargo run -p ld-server`. Le .env n'est jamais commité.
|
||||
|
||||
# Backend
|
||||
SECRET_KEY=change-me-in-production-with-a-real-secret-key
|
||||
DEBUG=true
|
||||
# Écoute et dossiers
|
||||
LD_BIND=0.0.0.0:8002
|
||||
LD_DATA_DIR=data # SQLite, clone du dépôt des textes, clé de publication — données runtime
|
||||
LD_WEB_DIR=web/build # interface compilée (npm run build)
|
||||
LD_SEED_DIR=seeds
|
||||
LD_PUBLIC_URL=http://127.0.0.1:3002
|
||||
|
||||
# Environment: development, staging, production
|
||||
ENVIRONMENT=development
|
||||
# Connexion Ğ1 — client OAuth2 de connect.monnaie-libre.fr (les deux sont requis, sinon désactivée)
|
||||
# LD_CONNECT_URL=https://connect.monnaie-libre.fr
|
||||
# LD_CONNECT_CLIENT_ID=
|
||||
# LD_CONNECT_CLIENT_SECRET=
|
||||
|
||||
# Logging: DEBUG, INFO, WARNING, ERROR
|
||||
LOG_LEVEL=INFO
|
||||
# Accès (trustinWallet)
|
||||
LD_ADMIN_G1_IDS= # ex. idty:8352 — administrateurs des accès, séparés par des virgules
|
||||
LD_DEV_LOGIN=1 # dev local uniquement : connexion sans clé, membre de tous les groupes
|
||||
LD_BOOTSTRAP_ADMIN=0 # 1 = au premier démarrage sans admin, écrit data/bootstrap-admin.txt (0600)
|
||||
|
||||
# CORS
|
||||
CORS_ORIGINS=["http://localhost:3002"]
|
||||
# Publication des textes en vigueur (dépôt git lu par les applis clientes)
|
||||
# LD_PUBLISH_REMOTE=git@git.duniter.org:Yvv/libredecision-textes.git
|
||||
# LD_PUBLISH_BRANCH=main
|
||||
# LD_PUBLISH_KEY=data/keys/publish_ed25519 # générée au démarrage si absente
|
||||
# LD_PUBLISH_AUTHOR=libreDecision
|
||||
# LD_PUBLISH_EMAIL=decision@librodrome.org
|
||||
|
||||
# Database connection pool
|
||||
DATABASE_POOL_SIZE=20
|
||||
DATABASE_MAX_OVERFLOW=10
|
||||
# Taille de la toile de confiance (W) — lue sur la chaîne, repli sur l'indexeur puis sur la valeur fixe
|
||||
# LD_RPC_ENDPOINTS=https://rpc.duniter.org,https://g1.axiom-team.fr/ws/
|
||||
# LD_INDEXERS=https://indexer.duniter.org/v1/graphql
|
||||
# LD_WOT_FALLBACK=5745
|
||||
|
||||
# Auth / Sessions
|
||||
SESSION_TTL_HOURS=24
|
||||
CHALLENGE_EXPIRE_SECONDS=300
|
||||
|
||||
# Rate limiting (requests per minute)
|
||||
RATE_LIMIT_DEFAULT=60
|
||||
RATE_LIMIT_AUTH=10
|
||||
RATE_LIMIT_VOTE=30
|
||||
|
||||
# Duniter V2 RPC
|
||||
DUNITER_RPC_URL=wss://gdev.p2p.legal/ws
|
||||
DUNITER_RPC_TIMEOUT_SECONDS=10
|
||||
|
||||
# Blockchain cache TTL (seconds)
|
||||
BLOCKCHAIN_CACHE_TTL_SECONDS=3600
|
||||
|
||||
# IPFS
|
||||
IPFS_API_URL=http://localhost:5001
|
||||
IPFS_GATEWAY_URL=http://localhost:8080
|
||||
IPFS_TIMEOUT_SECONDS=30
|
||||
|
||||
# Frontend
|
||||
NUXT_PUBLIC_API_BASE=http://localhost:8002/api/v1
|
||||
|
||||
# Docker / Production
|
||||
DOMAIN=decision.librodrome.org
|
||||
RUST_LOG=info,sqlx=warn
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
# libreDecision v3 — le produit (Rust + Svelte) sur decision.librodrome.org.
|
||||
# Une seule image : la construction rejoue toute la boucle (fmt, clippy -D warnings,
|
||||
# tests Rust, svelte-check, vitest) — un contrôle rouge, pas d'image.
|
||||
# Données runtime : volume nommé lpk-libredecision-app_data (SQLite, clone du dépôt
|
||||
# des textes, clé de publication). Jamais `down -v`, jamais recréé.
|
||||
|
||||
when:
|
||||
- branch: main
|
||||
event: push
|
||||
|
||||
# La maquette libère d'abord la racine (elle part sur maquette.decision.librodrome.org) ;
|
||||
# le produit se déploie ensuite, même si la maquette a échoué.
|
||||
depends_on:
|
||||
- maquette
|
||||
runs_on: [success, failure]
|
||||
|
||||
steps:
|
||||
|
||||
- name: security-check
|
||||
image: alpine:3.20
|
||||
commands:
|
||||
- |
|
||||
if [ -f .env ]; then
|
||||
echo "ERREUR: .env ne doit pas etre commite dans le depot"
|
||||
exit 1
|
||||
fi
|
||||
- 'grep -q "^\.env$" .gitignore || (echo "ERREUR: .env manquant dans .gitignore" && exit 1)'
|
||||
- echo "Security check OK"
|
||||
|
||||
# NOTE: volumes + pas de from_secret : compatible
|
||||
- name: build-app
|
||||
image: docker:27-cli
|
||||
depends_on:
|
||||
- security-check
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
commands:
|
||||
- BUILD=$(echo "$CI_COMMIT_SHA" | cut -c1-8)
|
||||
- docker build -t libredecision-app:latest -f docker/app.Dockerfile --target production --build-arg LD_BUILD=$BUILD .
|
||||
- echo "Image app construite ($BUILD)"
|
||||
|
||||
# NOTE: volumes + pas de from_secret : compatible
|
||||
- name: sbom-generate
|
||||
image: alpine:3.20
|
||||
depends_on:
|
||||
- build-app
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
commands:
|
||||
- apk add --no-cache curl
|
||||
- curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin latest
|
||||
- mkdir -p .reports
|
||||
# spec 1.5 épinglée : syft « latest » émet plus récent que ce que le trivy du runner décode
|
||||
- syft libredecision-app:latest -o cyclonedx-json@1.5 --file .reports/sbom-app.cyclonedx.json
|
||||
- echo "SBOM genere"
|
||||
|
||||
# NOTE: volumes + pas de from_secret : compatible
|
||||
- name: sbom-scan
|
||||
image: aquasec/trivy:latest
|
||||
depends_on:
|
||||
- sbom-generate
|
||||
volumes:
|
||||
- /home/syoul/trivy-cache:/root/.cache/trivy
|
||||
commands:
|
||||
- trivy sbom --format json --output .reports/trivy-app.json .reports/sbom-app.cyclonedx.json
|
||||
- echo "Scan CVE termine"
|
||||
|
||||
# NOTE: from_secret + pas de volumes : compatible
|
||||
# failure: ignore — télémétrie, ne bloque pas le déploiement (cf. maquette.yml).
|
||||
- name: sbom-publish
|
||||
image: alpine/curl:latest
|
||||
failure: ignore
|
||||
depends_on:
|
||||
- sbom-scan
|
||||
environment:
|
||||
DTRACK_TOKEN:
|
||||
from_secret: dependency_track_token
|
||||
DTRACK_DOMAIN:
|
||||
from_secret: dtrack_domain
|
||||
commands:
|
||||
- |
|
||||
VERSION=$(date +%Y-%m-%d)-$(echo "$CI_COMMIT_SHA" | cut -c1-8)
|
||||
HTTP=$(curl -s -o /tmp/dtrack-resp.txt -w "%{http_code}" -X POST "https://$DTRACK_DOMAIN/api/v1/bom" \
|
||||
-H "X-Api-Key: $DTRACK_TOKEN" \
|
||||
-F "autoCreate=true" \
|
||||
-F "projectName=libredecision-app" \
|
||||
-F "projectVersion=$VERSION" \
|
||||
-F "bom=@.reports/sbom-app.cyclonedx.json")
|
||||
echo "HTTP $HTTP (app) : $(cat /tmp/dtrack-resp.txt)"
|
||||
[ "$HTTP" -ge 200 ] && [ "$HTTP" -lt 300 ] || exit 1
|
||||
|
||||
# NOTE: volumes + pas de from_secret : compatible
|
||||
# Fabio route via les labels SERVICE_* de docker/app-compose.yml.
|
||||
- name: deploy
|
||||
image: docker:27-cli
|
||||
depends_on:
|
||||
- sbom-publish
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- /opt/libredecision-app:/opt/libredecision-app
|
||||
commands:
|
||||
- DOMAIN=decision.librodrome.org
|
||||
- echo "APP_DOMAIN=$DOMAIN" > /opt/libredecision-app/.env
|
||||
- cp docker/app-compose.yml /opt/libredecision-app/docker-compose.yml
|
||||
# Tout conteneur qui revendique encore la racine (maquette pas encore déplacée,
|
||||
# ou en échec) est arrêté : la racine est au produit, et fabio partagerait le trafic.
|
||||
- |
|
||||
for TAG in "urlprefix-$DOMAIN/*" "urlprefix-$DOMAIN/api/*"; do
|
||||
for C in $(docker ps -q --filter "label=SERVICE_3000_TAGS=$TAG") $(docker ps -q --filter "label=SERVICE_8002_TAGS=$TAG"); do
|
||||
NAME=$(docker inspect --format '{{.Name}}' "$C")
|
||||
[ "$NAME" = "/lpk-libredecision-app" ] && continue
|
||||
echo "Arret de $NAME (revendique $TAG)"
|
||||
docker stop "$C"
|
||||
done
|
||||
done
|
||||
# Arrêter avant le challenge ACME : la route hôte /* masquerait le webroot de sonic-acme-1.
|
||||
- cd /opt/libredecision-app && docker compose stop
|
||||
- |
|
||||
ACME_EXIT=0
|
||||
docker exec sonic-acme-1 /app/acme.sh \
|
||||
--home /etc/acme.sh \
|
||||
--issue -d "$DOMAIN" \
|
||||
--webroot /usr/share/nginx/html \
|
||||
--server letsencrypt \
|
||||
--accountemail support+acme@asycn.io || ACME_EXIT=$?
|
||||
if [ "$ACME_EXIT" -eq 0 ] || [ "$ACME_EXIT" -eq 2 ]; then
|
||||
docker exec sonic-acme-1 cp /etc/acme.sh/$DOMAIN/fullchain.cer /host/certs/$DOMAIN-cert.pem
|
||||
docker exec sonic-acme-1 cp /etc/acme.sh/$DOMAIN/$DOMAIN.key /host/certs/$DOMAIN-key.pem
|
||||
echo "TLS OK (acme exit $ACME_EXIT)"
|
||||
elif docker exec sonic-acme-1 test -f "/host/certs/$DOMAIN-cert.pem"; then
|
||||
echo "AVERTISSEMENT: emission ACME en echec (exit $ACME_EXIT) - certificat existant conserve"
|
||||
else
|
||||
echo "ERREUR: acme.sh a echoue (exit $ACME_EXIT) et aucun certificat existant pour $DOMAIN"
|
||||
exit 1
|
||||
fi
|
||||
# Image construite localement dans la pipeline : pas de docker compose pull
|
||||
- cd /opt/libredecision-app && docker compose up -d --remove-orphans
|
||||
- cd /opt/libredecision-app && docker compose ps
|
||||
|
||||
- name: test-deploy
|
||||
image: docker:27-cli
|
||||
depends_on:
|
||||
- deploy
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
commands:
|
||||
- |
|
||||
STATUS=$(docker inspect --format '{{.State.Status}}' lpk-libredecision-app 2>/dev/null || echo "absent")
|
||||
echo "lpk-libredecision-app : $STATUS"
|
||||
[ "$STATUS" = "running" ] || { docker logs --tail 50 lpk-libredecision-app 2>&1; echo "FAIL: non running"; exit 1; }
|
||||
echo "PASS: conteneur running"
|
||||
|
||||
# Le produit répond, et c'est bien ce commit qui répond.
|
||||
- name: healthcheck
|
||||
image: alpine:3.20
|
||||
depends_on:
|
||||
- test-deploy
|
||||
commands:
|
||||
- apk add --no-cache --quiet curl
|
||||
- |
|
||||
BUILD=$(echo "$CI_COMMIT_SHA" | cut -c1-8)
|
||||
TARGET="https://decision.librodrome.org/api/health"
|
||||
MAX=60
|
||||
i=0
|
||||
until [ $i -ge $MAX ]; do
|
||||
BODY=$(curl -sS -m 10 "$TARGET" 2>/dev/null || true)
|
||||
echo "Tentative $((i+1))/$MAX - $BODY"
|
||||
case "$BODY" in
|
||||
*'"ok":true'*"\"build\":\"$BUILD\""*|*"\"build\":\"$BUILD\""*'"ok":true'*)
|
||||
echo "PASS: $BUILD repond sur $TARGET"; exit 0 ;;
|
||||
esac
|
||||
i=$((i+1))
|
||||
sleep 10
|
||||
done
|
||||
echo "FAIL: $BUILD ne repond pas apres 10 minutes"
|
||||
exit 1
|
||||
|
||||
- name: notify-failure
|
||||
image: alpine:3.20
|
||||
commands:
|
||||
- 'echo "ECHEC pipeline app #$CI_PIPELINE_NUMBER sur $CI_COMMIT_BRANCH ($CI_COMMIT_SHA)"'
|
||||
when:
|
||||
- status: failure
|
||||
@@ -1,3 +1,8 @@
|
||||
# La maquette Nuxt v2 (FastAPI + Postgres + IPFS), conservée telle quelle pour
|
||||
# présenter le projet. Depuis la v3 elle vit sur maquette.decision.librodrome.org ;
|
||||
# le produit (.woodpecker/app.yml) prend la racine decision.librodrome.org et
|
||||
# attend la fin de ce workflow, qui libère la racine.
|
||||
# Le nom de projet compose reste lpk-decision-main : mêmes volumes, aucune donnée perdue.
|
||||
when:
|
||||
- branch: main
|
||||
event: push
|
||||
@@ -18,7 +23,7 @@ steps:
|
||||
- name: test-backend
|
||||
image: python:3.11-slim
|
||||
commands:
|
||||
- cd backend
|
||||
- cd maquette/backend
|
||||
- pip install --no-cache-dir -r requirements.txt
|
||||
- pytest app/tests/ -v --tb=short
|
||||
|
||||
@@ -27,7 +32,7 @@ steps:
|
||||
commands:
|
||||
# git requis : dépendance @yvv/nuxt-base en git+https (node:20-slim n'a pas git)
|
||||
- apt-get update && apt-get install -y --no-install-recommends git ca-certificates
|
||||
- cd frontend
|
||||
- cd maquette/frontend
|
||||
- npm ci
|
||||
- npm run build
|
||||
|
||||
@@ -39,7 +44,7 @@ steps:
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
commands:
|
||||
- docker build -t libredecision-backend:latest -f docker/backend.Dockerfile --target production .
|
||||
- docker build -t libredecision-backend:latest -f maquette/docker/backend.Dockerfile --target production maquette
|
||||
- echo "Image backend construite"
|
||||
|
||||
# NOTE: volumes + pas de from_secret : compatible
|
||||
@@ -50,7 +55,7 @@ steps:
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
commands:
|
||||
- docker build -t libredecision-frontend:latest -f docker/frontend.Dockerfile --target production .
|
||||
- docker build -t libredecision-frontend:latest -f maquette/docker/frontend.Dockerfile --target production maquette
|
||||
- echo "Image frontend construite"
|
||||
|
||||
# NOTE: volumes + pas de from_secret : compatible
|
||||
@@ -116,14 +121,12 @@ steps:
|
||||
depends_on:
|
||||
- sbom-publish
|
||||
environment:
|
||||
APP_DOMAIN:
|
||||
from_secret: app_domain
|
||||
POSTGRES_PASSWORD:
|
||||
from_secret: postgres_password
|
||||
SECRET_KEY:
|
||||
from_secret: secret_key
|
||||
commands:
|
||||
- echo "APP_DOMAIN=$APP_DOMAIN" > .env.deploy
|
||||
- echo "APP_DOMAIN=maquette.decision.librodrome.org" > .env.deploy
|
||||
- echo "POSTGRES_PASSWORD=$POSTGRES_PASSWORD" >> .env.deploy
|
||||
- echo "SECRET_KEY=$SECRET_KEY" >> .env.deploy
|
||||
- OWNER=$(echo "$CI_REPO_OWNER" | tr 'A-Z' 'a-z') && REPO=$(echo "$CI_REPO_NAME" | tr 'A-Z' 'a-z') && BRANCH=$(echo "$CI_COMMIT_BRANCH" | tr 'A-Z/' 'a-z-') && echo "COMPOSE_PROJECT_NAME=$OWNER-$REPO-$BRANCH" >> .env.deploy
|
||||
@@ -158,7 +161,7 @@ steps:
|
||||
commands:
|
||||
- cp .env.deploy /opt/libredecision/.env
|
||||
- chmod 600 /opt/libredecision/.env
|
||||
- cp docker/docker-compose.yml /opt/libredecision/docker-compose.yml
|
||||
- cp maquette/docker/docker-compose.yml /opt/libredecision/docker-compose.yml
|
||||
# Arreter avant le challenge ACME : libere le webroot pour sonic-acme-1
|
||||
# L'org forge a ete renommee (EHV -> LPK) : le nom de projet compose a change,
|
||||
# on arrete donc aussi l'ancienne stack (ehv-decision-main-*) pour eviter la
|
||||
@@ -1,100 +1,100 @@
|
||||
# libreDecision
|
||||
|
||||
Boîte à outils de la première démocratie — décider ensemble, du trio à la population.
|
||||
v2 local-first : 6 routes de décision, 5 modalités, documents sous vote permanent, mandats,
|
||||
observatoire. Marque blanche par construction (seeds Ğ1 + Atelier du Canal).
|
||||
**Référence produit : `docs/dev/BLUEPRINT-V2.md`** — toute décision de comportement s'y adosse.
|
||||
Rendre possible la décision collective — en fonction du contexte, processus et modalités,
|
||||
boîte à outils. **v3 : Rust + Svelte**, un chantier à la fois, chacun livré en production avant
|
||||
le suivant (`docs/chantiers/README.md`).
|
||||
**Référence du chantier en cours : `docs/chantiers/acte-certification/CHANTIER.md`** — toute
|
||||
décision de comportement s'y adosse ; les décisions durables dans `docs/adr/`.
|
||||
La maquette v2 (Nuxt + FastAPI) vit dans `maquette/`, **intacte** (Yvv la présente) — ne pas y
|
||||
développer.
|
||||
|
||||
## Protocole de début de session
|
||||
|
||||
1. `git pull --rebase origin main`
|
||||
2. Si l'objectif de la session n'est pas précisé, le demander
|
||||
1. `git pull --rebase origin main` (puis la branche du chantier : `chantier/acte-certification`)
|
||||
2. Lire `HANDOFF.md` (où on en est, ce qui attend Yvv)
|
||||
3. Si l'objectif de la session n'est pas précisé, le demander
|
||||
|
||||
## Stack
|
||||
|
||||
- **Frontend** : Nuxt 4 (Vue 3, TypeScript, SPA — SSR off) + Nuxt UI v3 + Pinia + UnoCSS ;
|
||||
package manager : npm ; port **3002** strict
|
||||
- **Local-first** : IndexedDB (idb-keyval) via `app/data/persistence.ts` — une clé
|
||||
`ld2:<collectiveId>` par collectif, export/import de bundles JSON, seeds par le même chemin
|
||||
- **Backend v1** (FastAPI + SQLAlchemy async, port 8002) : conservé pour la **future synchro**
|
||||
— non requis pour le dev frontend ; `runtimeConfig.public.apiBase` gardé dans nuxt.config
|
||||
en prévision
|
||||
- Déploiement : Docker multi-stage + Traefik ; CI Woodpecker
|
||||
- **Serveur** : Rust edition 2024, toolchain **1.99** épinglée (`rust-toolchain.toml`) ;
|
||||
rustup installé sans PATH → `export PATH=$HOME/.cargo/bin:$PATH`. axum 0.8, sqlx 0.8
|
||||
(SQLite, requêtes runtime + `migrate!`), tokio, reqwest rustls.
|
||||
- **Interface** : SvelteKit 2 / Svelte 5 (runes), adapter-static SPA, vite 7, vitest,
|
||||
Playwright ; npm. Polices Literata (textes) + Nunito (interface), fontsource.
|
||||
- **Ports** : dev interface **3002** (strict, proxy `/api` → 8002) · serveur **8002** ·
|
||||
serveur jetable des e2e **8092**.
|
||||
- **Déploiement** : une image (`docker/app.Dockerfile`), Woodpecker `ci.open.us.org` dépôt
|
||||
LPK/decision → sonic (fabio). `.woodpecker/app.yml` = le produit sur
|
||||
**decision.librodrome.org** ; `.woodpecker/maquette.yml` = la maquette sur
|
||||
**maquette.decision.librodrome.org** (le produit attend la maquette, qui libère la racine).
|
||||
Push sur `prod` (`gitea@git.open.us.org:EHV/decision.git`) branche `main` = déploiement.
|
||||
|
||||
## Structure
|
||||
|
||||
```
|
||||
frontend/
|
||||
app/
|
||||
engine/ # moteurs PURS (zéro dépendance Nuxt) : threshold (+electionResult),
|
||||
# nuanced, modeParams, parametric (+crystallize), settings, state (canTransition),
|
||||
# triage, impact — import UNIQUEMENT via le barrel ~/engine
|
||||
data/
|
||||
persistence.ts # IndexedDB local-first
|
||||
templates.ts # 7 gabarits de collectif (invariant : protocole Consentement partout)
|
||||
seeds/ # duniter-g1.bundle.json (324 Ko) + atelier-du-canal.bundle.json
|
||||
stores/
|
||||
collective.ts # le tenant : collectif actif, état complet, cycle de vie des bundles
|
||||
decisions.ts # façade d'actions sans état propre (LWW updatedAt, persist debounced)
|
||||
composables/ # useMood (useLibreMood), useFeed (le Fil = sélecteur pur), useSearch
|
||||
components/ # par domaine : chemin/ decisions/ votes/ texts/ mandates/ feed/
|
||||
# onboarding/ common/ (primitives Ld*)
|
||||
pages/ # 14 routes : / · decider · decisions (+[id], vote, observatoire) ·
|
||||
# textes (+[slug], formules) · mandats (+[id], nouveau) ·
|
||||
# creer (layout bare) · donnees
|
||||
lexicon.ts # SOURCE UNIQUE des libellés FR + FORBIDDEN_UI_TERMS
|
||||
types/domain.ts # le noyau du modèle
|
||||
tests/ # 342 tests vitest : engine/, stores/, seeds/ (non-perte), lexicon.spec.ts
|
||||
backend/ # v1 FastAPI conservé pour la synchro future — ne pas développer dessus
|
||||
scripts/export_seed_bundle.py # extrait seed.py → duniter-g1.bundle.json (source vivante = bundle)
|
||||
docker/ # compose + Dockerfiles
|
||||
docs/dev/BLUEPRINT-V2.md # la référence produit v2
|
||||
public/hexagram-tsing*.svg # sceau 井 (#48 Tsing)
|
||||
crates/
|
||||
ld-engine/ # PUR (zéro I/O) : threshold (formule inertielle, presets), edit (coquille vs fond,
|
||||
# diff), assist (découpage, similarité, longueur), compose (adoption, aperçus)
|
||||
trustin-access/ # règles d'accès de trustinWallet : sees / opens / manages_access, groupes par défaut
|
||||
ld-server/
|
||||
migrations/ # schéma SQLite — additif seulement
|
||||
src/ # lib.rs (routes, csrf, run) · config.rs (LD_*) · auth.rs (connect OAuth2, invitations,
|
||||
# dev, bootstrap) · docs.rs (lecture, aperçus) · proposals.rs (écrire, voter, signaler,
|
||||
# traduire) · adoption.rs (balayage, verrou) · publish.rs (file → git) · wot.rs (W
|
||||
# sur la chaîne) · seed.rs · web.rs (sert web/build)
|
||||
tests/api.rs # scénarios bout en bout
|
||||
seeds/ # acte-engagement-certification.json (trilingue, 33 articles, I1/E1/E2 en vigueur)
|
||||
web/src/
|
||||
lib/i18n/ # fr.ts SOURCE (types Key) · en.ts · es.ts — parité testée
|
||||
lib/components/ # Sidebar, DocPanel, VoteView, ProposeView, UnitBlock, DeepList, Seal, Stamp…
|
||||
routes/ # / · connexion · invitation · groupes · g/[group] · g/[group]/docs/[doc] (lecteur,
|
||||
# panneau dans l'URL ?onglet=&article=) · acces (trustinWallet) · decisions/mandats/
|
||||
# protocoles (à venir)
|
||||
web/e2e/ # Playwright C1–C10 + M1 ; serve.sh = serveur jetable /tmp/ld-e2e sur :8092
|
||||
docker/ # app.Dockerfile · app-compose.yml (sonic)
|
||||
.woodpecker/ # app.yml · maquette.yml
|
||||
docs/ # chantiers/ · adr/ · dev/BLUEPRINT-V2.md (doctrine de la maquette) · recherche/
|
||||
maquette/ # v2 intacte (frontend Nuxt, backend FastAPI, docker, docs-v1)
|
||||
```
|
||||
|
||||
## Données runtime
|
||||
## Données runtime — jamais écrasées
|
||||
|
||||
- **postgres-data** / **ipfs-data** : volumes Docker du backend v1 — jamais écrasés
|
||||
- `.env` racine : secrets backend — jamais commité
|
||||
- **Production** : volume `lpk-libredecision-app_data` (`/data` : `libredecision.db`, `textes/`
|
||||
= clone du dépôt publié, `keys/` = clé de publication, `bootstrap-admin.txt`). Jamais
|
||||
`down -v`, jamais recréé.
|
||||
- **Maquette** : volumes `lpk-decision-main_postgres-data` / `_ipfs-data` (inchangés).
|
||||
- **Local** : `data/` (gitignoré) — le supprimer repart du seed. Les e2e utilisent `/tmp/ld-e2e`.
|
||||
- Un document seedé n'est **jamais** réinstancié : dès qu'il existe en base, c'est de la donnée.
|
||||
|
||||
## Commandes
|
||||
## Commandes — la boucle (tout vert = fait)
|
||||
|
||||
```bash
|
||||
# Frontend (tout le dev v2)
|
||||
cd frontend && npm run dev # :3002
|
||||
npm test # vitest — 342 tests, référence à maintenir
|
||||
npm run build # zéro erreur exigé avant commit
|
||||
export PATH=$HOME/.cargo/bin:$PATH
|
||||
cargo fmt --all --check && cargo clippy --workspace --all-targets -- -D warnings && cargo test --workspace
|
||||
cd web && npm run check && npm test && npm run build && npm run e2e # e2e : 11 contrôles
|
||||
|
||||
# Seeds : le bundle est la source vivante ; seed.py (backend) est remplacé par
|
||||
cd backend && .venv/bin/python scripts/export_seed_bundle.py # régénère duniter-g1.bundle.json
|
||||
# → test de non-perte bloquant : tests/seeds/duniter-g1.spec.ts
|
||||
|
||||
# Docker
|
||||
docker compose -f docker/docker-compose.yml up
|
||||
LD_DEV_LOGIN=1 cargo run -p ld-server # :8002, sert web/build
|
||||
cd web && npm run dev # :3002 à chaud
|
||||
docker build -f docker/app.Dockerfile --target production . # rejoue toute la boucle
|
||||
```
|
||||
|
||||
## Conventions / pièges
|
||||
|
||||
- **UI français, code anglais** (variables, commentaires, docstrings)
|
||||
- **lexicon.ts d'abord** : aucun libellé UI en dur si le lexicon peut le porter ; ton = tutoyer
|
||||
la personne, sujet grammatical = la personne ou le collectif, jamais la formule ni le système
|
||||
- **Anti-lexique** (`tests/lexicon.spec.ts`) : `FORBIDDEN_UI_TERMS` scanné sur lexicon +
|
||||
templates `.vue` (insensible casse/accents) — « triage », « verdict », « déléguer »… restent
|
||||
des identifiants de code, jamais des textes affichés
|
||||
- **Invariants doctrinaux** : jamais de score sur une personne (attributs jamais affichés à
|
||||
autrui, re-votes visibles du seul auteur) ; jamais de contenu adopté sans geste humain
|
||||
(cristallisation, clôture de dossier, départage = gestes datés) ; repli universel → consent ;
|
||||
dérogation asymétrique (alourdir libre, alléger motivé)
|
||||
- **Budget 400 lignes max par page** (`app/pages/`)
|
||||
- **Moteurs purs** : engine/ sans Nuxt/Pinia/DOM, import via `~/engine` seulement ; stores en
|
||||
imports explicites (testables sous vitest nu) ; toute mutation d'état passe par
|
||||
`canTransition(decision, to, ctx)`
|
||||
- **Formule inertie** : `R = C + B^W + (M + (1−M)(1−(T/W)^G))·max(0, T−C)` —
|
||||
`frontend/app/engine/threshold.ts` ; l'Atelier (`/textes/formules`) est la seule maison des
|
||||
lettres W/T/M/B/G/C et de KaTeX
|
||||
- **Mood system** : `useLibreMood()` (`composables/useMood.ts`) — **jamais** de `:global()`
|
||||
dans `<style scoped>` pour les styles mood-dépendants (causa le bug dark mode veil)
|
||||
- **Sceau** `井` (#48 Tsing) : `LdSeal` dans les layouts ; SVGs dans `public/`
|
||||
- **CSS `drop-shadow()`** safe pour l'emboss ; `<filter>` SVG inline cause des artefacts
|
||||
- **Composants** : `pathPrefix: false` — noms de fichiers uniques, auto-import
|
||||
- **Domaine** : decision.librodrome.org (Woodpecker CI ; ancien dossier : Glibredecision)
|
||||
- **UI français, code anglais** ; **trilingue** : aucune chaîne affichée en dur, tout passe par
|
||||
`web/src/lib/i18n/fr.ts` puis en/es (test de parité + `FORBIDDEN_UI_TERMS`). Tutoyer ; le sujet
|
||||
est la personne ou le groupe, jamais le système. Le français du texte voté fait foi.
|
||||
- **Moteur pur** : une règle de vote / édition / composition s'écrit et se teste dans
|
||||
`ld-engine` avant d'être câblée. Le serveur ne décide jamais l'accès seul : `trustin-access`.
|
||||
- **Invariants** : une seule voix ne fait jamais passer un texte (C = 50 pour Monnaie libre) ;
|
||||
une coquille ne demande pas de vote, un changement de fond si ; un signalement de
|
||||
contradiction bloque ; l'inertie se vote au cran au-dessus ; le groupe d'essai ne publie jamais.
|
||||
- **Écritures** : en-tête `x-requested-with: libredecision` obligatoire (CSRF) ; cookie
|
||||
`ld_session`. Les tests d'API passent par `Client` (`tests/api.rs`).
|
||||
- **Pièges vus** : `pkill -f target/debug/ld-server` tue son propre shell → `pkill -x ld-server` ;
|
||||
un `from_secret` absent fait refuser tout le pipeline Woodpecker ; la route hôte `/*` de fabio
|
||||
masque le webroot ACME → arrêter le service avant `acme.sh --issue` ; Playwright en locale
|
||||
`fr-FR` (sinon l'interface part en anglais).
|
||||
- **Design** : sans bordures, arrondis 16/12/20/14, ambiances `pierre` / `nuit`
|
||||
(`data-mood`, posé avant le premier rendu), sceau hexagramme 井 #48 en SVG (`Seal`, `Stamp` —
|
||||
jamais le glyphe, qui s'affiche mal). Mouvement réduit respecté.
|
||||
- **Domaine** : decision.librodrome.org ; dépôt des textes `git.duniter.org/Yvv/libredecision-textes`.
|
||||
|
||||
+52
-80
@@ -1,91 +1,63 @@
|
||||
# Contribuer à libreDecision
|
||||
|
||||
La référence produit est `docs/dev/BLUEPRINT-V2.md` — toute contribution qui touche au
|
||||
comportement doit s'y conformer (ou proposer un amendement du blueprint, argumenté).
|
||||
On travaille **un chantier à la fois**, livré complet et en production avant le suivant
|
||||
(`docs/chantiers/README.md`). Le chantier en cours a sa fiche :
|
||||
`docs/chantiers/acte-certification/CHANTIER.md` — toute décision de comportement s'y adosse,
|
||||
ou propose de l'amender, argumentée.
|
||||
|
||||
## Environnement
|
||||
Première fois ? Commence par `docs/chantiers/acte-certification/ONBOARDING.md`.
|
||||
|
||||
## Les branches — travailler à deux
|
||||
|
||||
```
|
||||
main = la production. Tout push sur prod/main déploie decision.librodrome.org.
|
||||
chantier/acte-certification = la branche commune du binôme (Yvv · Chiara), toujours verte.
|
||||
chiara/<sujet>, yvv/<sujet> = une branche courte par sujet, partie de la branche du chantier.
|
||||
```
|
||||
|
||||
1. `git switch chantier/acte-certification && git pull --rebase origin chantier/acte-certification`
|
||||
2. `git switch -c chiara/<sujet>` — un sujet, une branche, quelques commits.
|
||||
3. Boucle verte (ci-dessous), puis push de la branche et demande de fusion vers
|
||||
`chantier/acte-certification` sur la forge (`git.open.us.org/yvv/decision`).
|
||||
4. Le chantier passe dans `main` quand la boucle est verte et les contrôles d'usage passés.
|
||||
|
||||
On ne pousse jamais directement sur `main`, et jamais de `--force` sur une branche partagée.
|
||||
|
||||
## La boucle — une tâche n'est faite que si tout est vert
|
||||
|
||||
```bash
|
||||
# Frontend — suffit pour tout le développement v2 (local-first, aucun serveur requis)
|
||||
cd frontend
|
||||
npm install
|
||||
npm run dev # http://localhost:3002 — port strict, jamais de fallback
|
||||
npm test # vitest — 342 tests
|
||||
npm run build # doit passer sans erreur avant tout commit
|
||||
|
||||
# Backend v1 (optionnel — conservé pour la future synchro)
|
||||
cd backend
|
||||
python -m venv venv && source venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
export PATH=$HOME/.cargo/bin:$PATH
|
||||
cargo fmt --all --check
|
||||
cargo clippy --workspace --all-targets -- -D warnings
|
||||
cargo test --workspace
|
||||
cd web
|
||||
npm run check # svelte-check, zéro avertissement
|
||||
npm test # vitest : markdown, parité des catalogues i18n, anti-lexique
|
||||
npm run build
|
||||
npm run e2e # Playwright : C1–C10 (bureau) + M1 (mobile), serveur jetable sur :8092
|
||||
```
|
||||
|
||||
L'image Docker rejoue les mêmes portes : un contrôle rouge, pas d'image, pas de déploiement.
|
||||
|
||||
## Conventions
|
||||
|
||||
- **UI en français, code en anglais** (variables, commentaires, docstrings).
|
||||
- **`app/lexicon.ts` est la source unique des libellés français.** Aucun libellé UI en dur
|
||||
dans un composant s'il existe (ou devrait exister) dans le lexicon. Ton : tutoyer la
|
||||
personne dans ses gestes ; actes collectifs à l'infinitif ; le sujet grammatical est
|
||||
toujours la personne ou le collectif — jamais la formule ni le système.
|
||||
- **Test anti-lexique** (`tests/lexicon.spec.ts`) : la liste `FORBIDDEN_UI_TERMS` interdit
|
||||
certains mots dans toute chaîne visible (lexicon + templates `.vue`), insensible à la casse
|
||||
et aux accents. « Triage », « verdict », « déléguer »… peuvent exister comme identifiants de
|
||||
code, jamais à l'écran.
|
||||
- **CSS** : borderless (profondeur via background + box-shadow), palettes par classes
|
||||
`.mood-*` sur `<html>`, composants natifs (`<input>`, `<select>`, `<button>` custom).
|
||||
- **Composants** : `pathPrefix: false` — noms de fichiers uniques et parlants, auto-import.
|
||||
- **Interface en français, code en anglais** (identifiants, commentaires, messages de log).
|
||||
- **Trilingue dès le départ** : toute chaîne affichée vit dans `web/src/lib/i18n/fr.ts`
|
||||
(la source), puis `en.ts` et `es.ts`. Le test de parité refuse une clé manquante ou un
|
||||
paramètre `{x}` qui diffère. Le texte voté est le français ; les traductions sont éditoriales.
|
||||
- **Ton** : tutoyer la personne ; le sujet est la personne ou le collectif, jamais le système.
|
||||
Mots interdits à l'écran (`FORBIDDEN_UI_TERMS`) : « triage », « verdict », « déléguer »…
|
||||
- **Moteur pur** : `crates/ld-engine` n'a ni I/O, ni base, ni réseau. Toute règle de vote,
|
||||
d'édition ou de composition s'y écrit et s'y teste d'abord.
|
||||
- **Accès** : le serveur ne décide jamais seul qui voit quoi — il demande à `trustin-access`.
|
||||
- **Données runtime** (`data/`, volume `lpk-libredecision-app_data`) : jamais écrasées, jamais
|
||||
réinitialisées ; une migration ajoute, elle ne réécrit pas.
|
||||
- **Design** : sans bordures (profondeur par fond et ombre), arrondis 16 / 12 / 20 / 14 px,
|
||||
deux ambiances (pierre, nuit), Literata pour les textes, Nunito pour l'interface.
|
||||
- **Commits** : en français, une intention par commit, `git add` avec chemins explicites.
|
||||
|
||||
## Invariants doctrinaux (non négociables)
|
||||
## Signaler
|
||||
|
||||
- **Jamais de score sur une personne.** Pas de note, pas de réputation, pas d'agrégat
|
||||
individuel. `Person.attributes` ne sont jamais affichés à autrui ni agrégés ; l'historique
|
||||
de re-vote n'est visible que de son auteur.
|
||||
- **Jamais de contenu adopté sans geste humain.** Le moteur constate des seuils sur des
|
||||
contenus formulés par des humains ; la cristallisation paramétrique, la clôture d'un
|
||||
dossier découpé, le départage d'une égalité sont des gestes datés — jamais des automatismes.
|
||||
- **Le repli universel est le consentement** : tout collectif a un protocole Consentement
|
||||
(`protocolByRange.consent`), tout repli de résolution y mène.
|
||||
- **Dérogation asymétrique** : alourdir libre (1 tap journalisé) ; alléger = note obligatoire
|
||||
+ fenêtre d'objection.
|
||||
- **Budget 400 lignes maximum par page** (`app/pages/`). Au-delà, extraire des composants.
|
||||
- **Moteurs purs** : `app/engine/` ne dépend de rien (ni Nuxt, ni Pinia, ni DOM), s'importe
|
||||
uniquement via le barrel `~/engine`, et chaque module a ses tests vitest. Les stores
|
||||
s'écrivent avec des imports explicites (pas d'auto-import Nuxt) pour rester testables sous
|
||||
vitest pur.
|
||||
|
||||
## Ajouter une modalité de vote
|
||||
|
||||
1. `app/types/domain.ts` — étendre `VoteMethod` et les types de session/vote nécessaires.
|
||||
2. `app/engine/` — le calcul dans un module pur + ses tests (`tests/engine/`). Exporter via
|
||||
`engine/index.ts`.
|
||||
3. `app/engine/state.ts` — la règle de clôture (automatique à `closesAt`, ou geste humain).
|
||||
4. `app/lexicon.ts` — les libellés (et vérifier l'anti-lexique).
|
||||
5. `app/components/votes/VoteX.vue` — l'écran de la modalité, branché dans
|
||||
`pages/decisions/[id]/vote.vue`.
|
||||
6. Seeds et gabarits — un protocole nommé qui la référence (le modifier = amender le Pacte).
|
||||
|
||||
## Ajouter un gabarit de collectif
|
||||
|
||||
Dans `app/data/templates.ts` : une entrée `TEMPLATE_CARDS` + le montage dans
|
||||
`buildTemplateBundle()`. Invariants : protocole Consentement au minimum, clause A1
|
||||
(« Notre finalité »), préambule Autonomie–Équilibre–Liaison proposé et modifiable, cercles
|
||||
typés `kind`. Un bundle seed passe par le même `importBundle()` que l'import utilisateur.
|
||||
|
||||
## Tests
|
||||
|
||||
```bash
|
||||
cd frontend && npm test
|
||||
```
|
||||
|
||||
- `tests/engine/` — les moteurs purs (seuils, triage, états, médiane, impact…).
|
||||
- `tests/stores/` — la façade decisions sous Pinia nu.
|
||||
- `tests/seeds/` — tests de non-perte : le bundle Ğ1 doit conserver 33+59 clauses, décomptes
|
||||
et provenance exacts ; l'Atelier du Canal doit couvrir tous les états.
|
||||
- `tests/lexicon.spec.ts` — l'anti-lexique.
|
||||
|
||||
Le compteur de référence est **342 tests verts**. Toute PR qui en casse un est refusée ;
|
||||
toute fonctionnalité de moteur arrive avec les siens.
|
||||
|
||||
## Build
|
||||
|
||||
`npm run build` doit rester vert (zéro erreur, zéro warning nouveau) avant chaque commit.
|
||||
Vérifier mobile + desktop (zéro régression CSS).
|
||||
**« Faire un retour »** (dans le menu de gauche, sur chaque page) envoie une note datée avec la
|
||||
page, la langue et la taille de l'écran ; elles se lisent dans trustinWallet (`/acces`).
|
||||
Pour un bug reproductible, une issue sur la forge avec les étapes.
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
# HANDOFF — libreDecision
|
||||
|
||||
> Dernière mise à jour : 2026-10-21, session orch du chantier 1.
|
||||
|
||||
## Où on en est
|
||||
|
||||
**Chantier 1 — Acte d'engagement Certification** : construit, boucle verte, prêt à livrer.
|
||||
|
||||
- v3 Rust + Svelte à la racine du dépôt ; la maquette v2 intacte dans `maquette/`.
|
||||
- Boucle : Rust (fmt, clippy `-D warnings`, 54 tests dont 13 scénarios d'API et la
|
||||
publication vers un dépôt nu) · interface (svelte-check 0 avertissement, 9 vitest) ·
|
||||
e2e 11 / 11 (C1–C10, M1). L'image Docker rejoue tout et a été essayée localement :
|
||||
santé, interface, volume non-root, clé de publication, lien admin initial (0600, absent
|
||||
des journaux), publication immédiate au démarrage, redémarrage sans commit superflu.
|
||||
- Déploiement : `.woodpecker/maquette.yml` (la maquette part sur
|
||||
maquette.decision.librodrome.org) puis `.woodpecker/app.yml` (le produit à la racine).
|
||||
|
||||
## Ce qui attend Yvv
|
||||
|
||||
1. **Envoyer la demande à poka** : `docs/chantiers/acte-certification/DEMANDE-CONNECT.md`.
|
||||
2. **Créer le dépôt des textes** sur git.duniter.org : `Yvv/libredecision-textes`, **public**,
|
||||
vide ; y ajouter la **clé de déploiement** du serveur, **en écriture** (sa partie publique
|
||||
s'affiche dans trustinWallet, `/acces`, section Publication). Le serveur fait lui-même le
|
||||
premier envoi.
|
||||
3. **Ouvrir trustinWallet** avec le lien administrateur initial (remis hors conversation),
|
||||
puis créer l'**invitation de Chiara** (groupes Monnaie libre + groupe d'essai).
|
||||
4. Donner à Chiara un compte **git.open.us.org** collaboratrice de `yvv/decision`.
|
||||
5. Décider : la **licence** du code ; le texte F1.2 du seed (annonce C = 0 et 30 jours).
|
||||
|
||||
## Pour reprendre
|
||||
|
||||
- Lire `docs/chantiers/acte-certification/CHANTIER.md` puis `CONTROLES.md`.
|
||||
- Les retours de Chiara arrivent dans `/acces` → Retours : chacun est un contrôle à repasser.
|
||||
- Quand les identifiants connect arrivent : `DEMANDE-CONNECT.md` § « À la réponse ».
|
||||
- Chantier suivant (après la mise en production et les retours) : Acte d'engagement forgeron.
|
||||
@@ -1,11 +1,10 @@
|
||||
# libreDecision
|
||||
|
||||
Boîte à outils de la première démocratie — décider ensemble, du trio à la population.
|
||||
Rendre possible la décision collective — en fonction du contexte, processus et modalités,
|
||||
boîte à outils.
|
||||
|
||||
libreDecision fait de la décision un geste quotidien : chaque question tient en une phrase,
|
||||
l'outil propose le chemin le plus court — jamais plus lourd que l'enjeu — et tout ce qui est
|
||||
décidé reste révisable, mesuré, consigné. Le même outil sert un trio d'amis, une association,
|
||||
une coopérative ou la toile de confiance Ğ1 (marque blanche par construction).
|
||||
En ligne : **https://decision.librodrome.org** (le produit, v3) · la maquette v2, conservée pour
|
||||
présenter le projet : https://maquette.decision.librodrome.org (`maquette/`).
|
||||
|
||||
## Manifeste
|
||||
|
||||
@@ -24,90 +23,92 @@ une coopérative ou la toile de confiance Ğ1 (marque blanche par construction).
|
||||
> Autonomie, Équilibre, Liaison. Du trio d'amis à la toile Ğ1 : le même geste, dix secondes,
|
||||
> et l'observatoire qui apprend de nous. La ville change, le puits demeure.
|
||||
|
||||
## Les 6 routes de décision
|
||||
## Les quatre outils
|
||||
|
||||
Le triage (moteur pur, toujours expliqué en une phrase, toujours contournable) oriente chaque
|
||||
décision vers l'une de six routes :
|
||||
| Outil | Ce qu'il fait | État |
|
||||
|---|---|---|
|
||||
| **Décisions** | funnel assisté pour choisir les modalités, journal des décisions | à venir |
|
||||
| **Docs de référence** | acte d'engagement, licence, charte, constitution… sous vote permanent | **actif** |
|
||||
| **Mandats** | création et révocation de tout mandat | à venir |
|
||||
| **Protocoles** | workflows sans organe statutaire, rôles tournants, automatisations | à venir |
|
||||
|
||||
| Route | Geste |
|
||||
|---|---|
|
||||
| **solo** | « Personne d'autre n'est concerné — décide. » 2 gestes, moins de 10 secondes, trace facultative |
|
||||
| **record** | « Déjà tranché — je le consigne. » L'observatoire en un geste, adopté immédiatement |
|
||||
| **mandate** | « Ton mandat couvre — décide, c'est tracé. » Trace obligatoire + fenêtre d'objection 48 h |
|
||||
| **transmit** | « Le mandat d'un autre couvre — transmets-lui. » (ou conteste le mandat) |
|
||||
| **advice** | « Réversible et petit cercle — demande leur avis, puis décide. » 72 h |
|
||||
| **collective** | « Nous décidons. » Formulation, liste des concernés arrêtée, session de vote |
|
||||
Le produit avance **un chantier à la fois**, chacun livré complet et en production avant le
|
||||
suivant — feuille de route : [`docs/chantiers/README.md`](docs/chantiers/README.md).
|
||||
Chantier en cours : **Acte d'engagement Certification** (Monnaie libre), en binôme Yvv + Chiara.
|
||||
|
||||
Dérogation asymétrique : alourdir est libre (1 tap journalisé) ; alléger exige une note motivée
|
||||
et une fenêtre d'objection.
|
||||
## Comment marche un doc de référence
|
||||
|
||||
## Les 5 modalités
|
||||
- On arrive sur **le texte en vigueur**, seul, lisible. Trois gestes contextuels : consulter le
|
||||
vote, voir les propositions, proposer une modification.
|
||||
- Chaque article est sous **vote permanent** : une proposition qui atteint son seuil (formule
|
||||
inertielle Ğ1, plancher C de votants « pour ») **remonte en vigueur** et remplace la version
|
||||
en place — si elle tient une marge plus large. Les textes de départ sont des *seeds a minima*,
|
||||
à zéro vote.
|
||||
- **Corriger n'est pas voter** : une coquille se corrige sans vote ; un changement de fond
|
||||
ouvre une nouvelle révision, dont les votes antérieurs sont à reconfirmer.
|
||||
- Deux propositions qui **se contredisent** ne passent pas ensemble : un signalement bloque la
|
||||
seconde tant qu'il n'est pas levé.
|
||||
- L'**inertie** de chaque article se vote aussi (par défaut : un cran au-dessus).
|
||||
- Tout ce qui entre en vigueur est **publié automatiquement** dans un dépôt git
|
||||
(fr/en/es + JSON) lu par les applications clientes.
|
||||
|
||||
Chaque protocole est nommé et référencé par une clause du Pacte — le modifier, c'est amender
|
||||
le Pacte.
|
||||
|
||||
1. **Consentement** — « Ça me va / J'objecte » : zéro objection maintenue = adopté. Le repli
|
||||
universel : tout collectif en a un (invariant).
|
||||
2. **Nuancé 6 niveaux** — de « Pas du tout » à « Tout à fait », jamais « contre » ;
|
||||
histogramme visible, commentaire obligatoire sur les niveaux bas.
|
||||
3. **Paramétrique** — décider au curseur : faisceau des positions, médiane vivante, carte
|
||||
« Pour moi » ; la cristallisation finale est un geste humain daté du garant, jamais un
|
||||
automatisme.
|
||||
4. **Élection** — réservée aux mandats et aux problématiques d'échelle (6 méthodes de
|
||||
nomination, vote blanc, jamais de départage par l'outil).
|
||||
5. **Binaire inertiel** — le seuil `C + B^W + (M + (1−M)(1−(T/W)^G))·max(0, T−C)`, héritage
|
||||
Ğ1 remis à sa place : l'outil de dernier recours des très grands corps. L'Atelier des
|
||||
formules (`/textes/formules`) reste son école.
|
||||
|
||||
## Local-first
|
||||
|
||||
- **Aucun serveur requis** : tout l'état vit dans le navigateur (IndexedDB via idb-keyval,
|
||||
une clé par collectif).
|
||||
- **Export / import** de bundles JSON — la fédération, c'est l'échange de bundles, jamais une
|
||||
table.
|
||||
- **Seeds** livrés et importés par le même chemin que l'import utilisateur :
|
||||
- `duniter-g1.bundle.json` — la toile Ğ1 : 33 clauses Certification + 59 clauses Forgeron
|
||||
(textes votés intacts, provenance et décomptes conservés), protocoles, 3 mandats ;
|
||||
- `atelier-du-canal.bundle.json` — association fictive de 12 membres, le test permanent de
|
||||
marque blanche : décisions à tous les états, paramétrique vivant, épreuve du réel due.
|
||||
- **7 gabarits** de collectif à la création (`/creer`) : page blanche, informel, association,
|
||||
coopérative, commune, monnaie libre, institution symétrique.
|
||||
Le détail, les décisions et leurs raisons : [`docs/chantiers/acte-certification/CHANTIER.md`](docs/chantiers/acte-certification/CHANTIER.md)
|
||||
et [`docs/adr/`](docs/adr/).
|
||||
|
||||
## Stack
|
||||
|
||||
- **Frontend** : Nuxt 4 (SPA, SSR off) + Vue 3 + TypeScript + Nuxt UI v3 + Pinia + UnoCSS ;
|
||||
npm ; port **3002**
|
||||
- **Moteurs purs** : `frontend/app/engine/` — triage, machine à états, seuils, médiane
|
||||
paramétrique, nuancé, périmètres — zéro dépendance Nuxt, testés vitest (**342 tests**)
|
||||
- **Backend v1** (FastAPI + SQLAlchemy async) : conservé dans `backend/` pour la future
|
||||
synchronisation multi-appareils — non requis pour utiliser ou développer le frontend
|
||||
- **Serveur** : Rust (edition 2024, toolchain 1.99 épinglée) — axum 0.8, sqlx/SQLite, tokio.
|
||||
Trois crates : `ld-engine` (moteur pur : seuils, édition, assistant, composition),
|
||||
`trustin-access` (règles d'accès de trustinWallet), `ld-server` (API + publication + service
|
||||
de l'interface).
|
||||
- **Interface** : SvelteKit 2 / Svelte 5 (runes), SPA statique servie par le serveur ;
|
||||
trilingue fr · en · es dès le départ (le français fait foi).
|
||||
- **Déploiement** : une seule image Docker, Woodpecker → sonic (fabio), volume de données nommé.
|
||||
|
||||
## Démarrage
|
||||
## Démarrer
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
npm install
|
||||
npm run dev # http://localhost:3002
|
||||
npm test # vitest — 342 tests
|
||||
npm run build # build de production
|
||||
export PATH=$HOME/.cargo/bin:$PATH
|
||||
cd web && npm ci && npm run build && cd .. # l'interface compilée
|
||||
LD_DEV_LOGIN=1 cargo run -p ld-server # API + interface sur :8002
|
||||
cd web && npm run dev # interface à chaud sur :3002 (proxy /api → :8002)
|
||||
```
|
||||
|
||||
## Structure v2
|
||||
`LD_DEV_LOGIN=1` ouvre une connexion de développement (sans clé, membre de tous les groupes) —
|
||||
jamais en production. Toutes les variables : [`.env.example`](.env.example).
|
||||
|
||||
## Vérifier (la boucle)
|
||||
|
||||
```bash
|
||||
cargo fmt --all --check && cargo clippy --workspace --all-targets -- -D warnings && cargo test --workspace
|
||||
cd web && npm run check && npm test && npm run build && npm run e2e
|
||||
```
|
||||
|
||||
Les contrôles d'usage (C1–C10, M1) sont décrits dans
|
||||
[`docs/chantiers/acte-certification/CONTROLES.md`](docs/chantiers/acte-certification/CONTROLES.md) ;
|
||||
les tests de Chiara en sont la version humaine.
|
||||
|
||||
## Structure
|
||||
|
||||
```
|
||||
frontend/app/
|
||||
engine/ # moteurs purs (barrel index.ts — seul point d'import)
|
||||
data/ # persistence.ts (IndexedDB), templates.ts (7 gabarits), seeds/*.bundle.json
|
||||
stores/ # collective.ts (le tenant) + decisions.ts (façade d'actions)
|
||||
composables/ # useMood, useFeed, useSearch
|
||||
components/ # par domaine : chemin/ decisions/ votes/ texts/ mandates/ feed/ onboarding/ common/
|
||||
pages/ # 14 routes : / decider decisions(+id, vote, observatoire)
|
||||
# textes(+slug, formules) mandats(+id, nouveau) creer donnees
|
||||
lexicon.ts # source unique des libellés FR + test anti-lexique
|
||||
types/domain.ts # le noyau du modèle
|
||||
docs/dev/BLUEPRINT-V2.md # la référence produit
|
||||
crates/
|
||||
ld-engine/ # moteur pur, sans I/O : threshold, edit, assist, compose
|
||||
trustin-access/ # qui voit, qui ouvre, qui gère les accès
|
||||
ld-server/ # axum : auth Ğ1 + invitations, docs, propositions, votes, adoption, publication
|
||||
migrations/ # schéma SQLite (données runtime : jamais réécrites)
|
||||
tests/api.rs # scénarios bout en bout de l'API
|
||||
seeds/ # documents de départ (JSON trilingue), instanciés une seule fois par groupe
|
||||
web/
|
||||
src/lib/ # api, session, i18n (fr source, en, es), composants
|
||||
src/routes/ # accueil, groupes, lecteur du texte, trustinWallet (/acces)…
|
||||
e2e/ # Playwright : C1–C10, M1
|
||||
docker/ # app.Dockerfile + app-compose.yml (sonic)
|
||||
.woodpecker/ # app.yml (le produit) · maquette.yml (la maquette)
|
||||
docs/ # chantiers/, adr/, dev/ (blueprint v2), recherche/
|
||||
maquette/ # la v2 Nuxt + FastAPI, telle quelle
|
||||
```
|
||||
|
||||
## Licence
|
||||
|
||||
Logiciel libre. Le fichier LICENSE sera posé avant la première publication.
|
||||
Logiciel libre — la licence reste à choisir (question ouverte à Yvv) ; le fichier LICENSE sera
|
||||
posé avant toute publication du code.
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
# libreDecision v3 on sonic — decision.librodrome.org.
|
||||
# Runtime data lives in the named volume `data` (SQLite, the server's clone of
|
||||
# the texts repository, its deploy key): never `down -v`, never recreated.
|
||||
# Settings that are not secret are here; secrets, if any, in /opt/libredecision-app/secrets.env.
|
||||
name: lpk-libredecision-app
|
||||
|
||||
services:
|
||||
app:
|
||||
image: libredecision-app:latest
|
||||
container_name: lpk-libredecision-app
|
||||
restart: always
|
||||
env_file:
|
||||
- path: /opt/libredecision-app/secrets.env
|
||||
required: false
|
||||
environment:
|
||||
LD_PUBLIC_URL: https://${APP_DOMAIN:-decision.librodrome.org}
|
||||
LD_ADMIN_G1_IDS: ${LD_ADMIN_G1_IDS:-idty:8352}
|
||||
LD_BOOTSTRAP_ADMIN: "1"
|
||||
LD_PUBLISH_REMOTE: ${LD_PUBLISH_REMOTE:-git@git.duniter.org:Yvv/libredecision-textes.git}
|
||||
LD_PUBLISH_BRANCH: main
|
||||
RUST_LOG: info,sqlx=warn,tower_http=warn
|
||||
volumes:
|
||||
- data:/data
|
||||
labels:
|
||||
# Registrator → Consul → Fabio.
|
||||
- SERVICE_8002_NAME=lpk-libredecision-app-8002
|
||||
- SERVICE_8002_TAGS=urlprefix-${APP_DOMAIN:-decision.librodrome.org}/*
|
||||
- SERVICE_8002_CHECK_TCP=true
|
||||
networks:
|
||||
- sonic
|
||||
|
||||
volumes:
|
||||
data:
|
||||
|
||||
networks:
|
||||
sonic:
|
||||
external: true
|
||||
@@ -0,0 +1,56 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
# libreDecision v3 — one image: the Rust server serves the API and the compiled
|
||||
# Svelte interface. Every gate of the loop runs inside the build: a red check
|
||||
# means no image.
|
||||
|
||||
# --- Interface -----------------------------------------------------------------
|
||||
FROM node:22-bookworm-slim AS web
|
||||
WORKDIR /src/web
|
||||
COPY web/package.json web/package-lock.json ./
|
||||
RUN npm ci --no-audit --no-fund
|
||||
COPY web/ ./
|
||||
ARG LD_BUILD=dev
|
||||
ENV LD_BUILD=${LD_BUILD}
|
||||
RUN npx svelte-kit sync \
|
||||
&& npx svelte-check --tsconfig ./tsconfig.json --fail-on-warnings \
|
||||
&& npx vitest run \
|
||||
&& npx vite build
|
||||
|
||||
# --- Server --------------------------------------------------------------------
|
||||
FROM rust:1.99-slim-bookworm AS server
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends git ca-certificates && rm -rf /var/lib/apt/lists/*
|
||||
WORKDIR /src
|
||||
COPY rust-toolchain.toml ./
|
||||
RUN rustup show active-toolchain || rustup toolchain install
|
||||
COPY Cargo.toml Cargo.lock rustfmt.toml ./
|
||||
COPY crates crates
|
||||
COPY seeds seeds
|
||||
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
||||
--mount=type=cache,target=/src/target \
|
||||
cargo fmt --all --check \
|
||||
&& cargo clippy --workspace --all-targets --locked -- -D warnings \
|
||||
&& cargo test --workspace --locked \
|
||||
&& cargo build --release --locked -p ld-server \
|
||||
&& cp target/release/ld-server /usr/local/bin/ld-server
|
||||
|
||||
# --- Runtime -------------------------------------------------------------------
|
||||
FROM debian:bookworm-slim AS production
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends ca-certificates git openssh-client tini \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& useradd --system --uid 10001 --create-home --home-dir /home/app app \
|
||||
&& mkdir -p /data && chown app:app /data
|
||||
COPY --from=server /usr/local/bin/ld-server /usr/local/bin/ld-server
|
||||
COPY --from=web /src/web/build /app/web
|
||||
COPY seeds /app/seeds
|
||||
ARG LD_BUILD=dev
|
||||
ENV LD_BUILD=${LD_BUILD} \
|
||||
LD_BIND=0.0.0.0:8002 \
|
||||
LD_DATA_DIR=/data \
|
||||
LD_WEB_DIR=/app/web \
|
||||
LD_SEED_DIR=/app/seeds
|
||||
USER app
|
||||
EXPOSE 8002
|
||||
VOLUME /data
|
||||
ENTRYPOINT ["tini", "--"]
|
||||
CMD ["ld-server"]
|
||||
@@ -0,0 +1,19 @@
|
||||
# 0001 — Rust + Svelte, une image, SQLite
|
||||
|
||||
**Contexte.** La v2 (Nuxt local-first + FastAPI) a servi de maquette. Yvv veut « un dev rust -
|
||||
svelte pour la longue durée, la performance, la modularité et la compilation de haute qualité ».
|
||||
Le premier chantier a besoin d'un serveur (votes partagés, publication), pas d'un stockage
|
||||
navigateur.
|
||||
|
||||
**Décision.**
|
||||
- Serveur en Rust : `ld-engine` (règles pures, sans I/O), `trustin-access` (accès),
|
||||
`ld-server` (axum + sqlx). Toolchain épinglée, `clippy -D warnings`, `unsafe` interdit.
|
||||
- Interface SvelteKit / Svelte 5 en SPA statique, compilée et servie par le même binaire.
|
||||
- SQLite (WAL) dans un volume nommé : une instance, des milliers de votants, aucune base à
|
||||
administrer. Migrations additives seulement.
|
||||
- Une seule image ; sa construction rejoue toute la boucle.
|
||||
- La maquette v2 reste dans `maquette/`, intacte, déployée à part.
|
||||
|
||||
**Coût.** Deux langages dans le dépôt ; une instance unique (pas de réplication). Si la
|
||||
synchronisation entre instances devient nécessaire, elle passera par le dépôt des textes et une
|
||||
API, pas par la base.
|
||||
@@ -0,0 +1,20 @@
|
||||
# 0002 — Vote permanent : marge du texte en place et plancher C
|
||||
|
||||
**Contexte.** Chaque article est sous vote permanent ; « dès qu'une proposition atteint le score,
|
||||
elle remonte en vigueur ». La formule inertielle Ğ1 donne un seuil faible quand peu de gens
|
||||
votent : sans garde-fou, une seule voix ferait passer un texte. Sans règle de départage, deux
|
||||
versions qui atteignent leur seuil s'échangeraient la place à chaque vote.
|
||||
|
||||
**Décision.**
|
||||
- Plancher **C = nombre minimal de votants « pour »** (Monnaie libre : 50 ; essai : 1), dans
|
||||
la formule (`R = C + …`) — choix d'Yvv.
|
||||
- Une proposition entre en vigueur si elle atteint son seuil **et** tient une marge
|
||||
(pour − requis) strictement plus large que la version en place. La version déplacée redevient
|
||||
une proposition avec ses votes : rien n'est perdu, tout peut revenir.
|
||||
- Les textes de départ sont des *seeds a minima* (0 vote, marge −C) : visibles, non adoptés.
|
||||
- Changer l'inertie d'un article est une proposition votée à l'inertie du cran au-dessus.
|
||||
- Le balayage tourne après chaque geste et toutes les 5 minutes, sous un verrou global.
|
||||
|
||||
**Coût.** Une version en place très soutenue est difficile à déloger — c'est voulu. La taille
|
||||
de la toile (W) bouge : un texte peut passer ou repasser sans nouveau vote ; chaque entrée en
|
||||
vigueur est datée et chiffrée dans `adoptions`.
|
||||
@@ -0,0 +1,15 @@
|
||||
# 0003 — Corriger n'est pas voter
|
||||
|
||||
**Contexte.** « Ça ne peut pas devenir un système qui nécessite un vote pour corriger une
|
||||
coquille. » Mais une correction ne doit pas pouvoir changer le sens d'un texte voté.
|
||||
|
||||
**Décision.** Le moteur classe chaque modification (`ld-engine/src/edit.rs`) :
|
||||
- **coquille** si le squelette (sans accents, casse, ponctuation, espaces) est identique, ou si
|
||||
au plus 2 mots diffèrent de 2 lettres au plus, sans chiffre ni mot de négation (ne, pas,
|
||||
jamais, personne…) ; elle s'applique sans vote, garde les votes et les traductions ;
|
||||
- **de fond** sinon : nouvelle révision d'une proposition ouverte (par son auteur), votes
|
||||
antérieurs « à reconfirmer », signalements levés ; refusée sur un texte en vigueur.
|
||||
L'éditeur montre le verdict avant d'enregistrer. Les brouillons s'éditent librement.
|
||||
|
||||
**Coût.** Le classement est prudent : certaines vraies coquilles (« persone » → « personne »)
|
||||
passent pour du fond et demandent une proposition. On préfère ce sens d'erreur.
|
||||
@@ -0,0 +1,14 @@
|
||||
# 0004 — Contradictions : signalées par les personnes, bloquantes
|
||||
|
||||
**Contexte.** « Il faut que 2 unités modulaires ne puissent pas passer en vigueur si elles se
|
||||
contredisent. » La contradiction est une question de sens : aucune règle ne la détecte sans
|
||||
se tromper, et l'IA est écartée pour l'instant.
|
||||
|
||||
**Décision.** Tout membre signale une contradiction entre deux propositions, avec un motif.
|
||||
Tant que le signalement tient, celle qui n'est pas en vigueur ne peut pas y entrer. Il se lève
|
||||
par son auteur ou un administrateur, et tombe si la proposition signalée change sur le fond.
|
||||
L'assistant, lui, prévient (sans bloquer) quand un texte porte plusieurs décisions ou ressemble
|
||||
à une proposition existante.
|
||||
|
||||
**Coût.** Un signalement abusif bloque une proposition jusqu'à sa levée. Les signalements sont
|
||||
publics, motivés, et l'administration peut les lever.
|
||||
@@ -0,0 +1,17 @@
|
||||
# 0005 — Publication par file d'envoi vers un dépôt git
|
||||
|
||||
**Contexte.** « Le tout est automatiquement poussé sur le dépôt git exploité par les applis
|
||||
clientes. » Le réseau ou la forge peuvent être indisponibles ; un vote ne doit jamais échouer
|
||||
pour autant.
|
||||
|
||||
**Décision.** Chaque entrée en vigueur (ou correction de coquille, ou traduction d'un texte en
|
||||
vigueur) ajoute une ligne à une file d'envoi en base ; au démarrage, chaque document publié y
|
||||
est remis une fois (rien ne se perd sur un redémarrage, un document inchangé ne fait pas de
|
||||
commit : sa date est celle du dernier changement, jamais « maintenant »). Un travailleur rend `fr.md`, `en.md`,
|
||||
`es.md` et `document.json` dans le clone du serveur, commit, pousse avec une clé de déploiement
|
||||
générée par le serveur (sa partie publique s'affiche dans trustinWallet), et réessaie avec un
|
||||
délai croissant. Dépôt : `git.duniter.org/Yvv/libredecision-textes`, un dossier par groupe et
|
||||
par document. Le français fait foi ; les traductions sont marquées éditoriales.
|
||||
|
||||
**Coût.** La publication est asynchrone (quelques secondes, plus en cas de panne) ; l'état
|
||||
de la file se lit dans trustinWallet.
|
||||
@@ -0,0 +1,20 @@
|
||||
# 0006 — Accès : connect.monnaie-libre.fr, invitations, trustinWallet
|
||||
|
||||
**Contexte.** « Le même accès que pour le forum Monnaie libre », « toute personne avec une clé
|
||||
Duniter peut se loger et tout faire — sauf la gestion des accès », qui revient à trustinWallet
|
||||
(nouveau nom de trustWallet, déjà pris).
|
||||
|
||||
**Décision.**
|
||||
- Connexion Ğ1 : libreDecision est client OAuth2 de connect.monnaie-libre.fr (comme le forum).
|
||||
Le compte est la clé d'identité (`idty:N`) ; on garde le nom affiché et le statut de membre.
|
||||
- En attendant l'inscription du client : liens d'invitation à usage unique (14 jours), le
|
||||
secret dans le fragment de l'URL (jamais dans les journaux du serveur).
|
||||
- Sessions : cookie HttpOnly, SameSite=Lax, Secure en https, 30 jours, jeton haché en base ;
|
||||
en-tête anti-CSRF sur toute écriture.
|
||||
- Les règles (qui voit, qui ouvre, qui administre) vivent dans `trustin-access`, futur cœur du
|
||||
service trustinWallet ; le serveur ne décide jamais seul.
|
||||
- Premier administrateur : un lien d'invitation écrit au premier démarrage dans le volume de
|
||||
données (0600), jamais dans les journaux.
|
||||
|
||||
**Coût.** Tant que connect n'est pas branché, chaque testeuse a besoin d'un lien. Quand
|
||||
trustinWallet deviendra un service, `trustin-access` deviendra son client.
|
||||
@@ -0,0 +1,13 @@
|
||||
# Décisions d'architecture
|
||||
|
||||
Une décision durable par fichier : le contexte, la décision, ce qu'elle coûte. On n'efface
|
||||
pas une décision : on en écrit une nouvelle qui la remplace, et on le note dans l'ancienne.
|
||||
|
||||
| # | Décision | Date |
|
||||
|---|---|---|
|
||||
| [0001](0001-rust-svelte.md) | Rust + Svelte, une image, SQLite | 2026-10-21 |
|
||||
| [0002](0002-vote-permanent.md) | Vote permanent : marge du texte en place et plancher C | 2026-10-21 |
|
||||
| [0003](0003-corriger-n-est-pas-voter.md) | Corriger n'est pas voter | 2026-10-21 |
|
||||
| [0004](0004-contradictions-signalees.md) | Contradictions : signalées par les personnes, bloquantes | 2026-10-21 |
|
||||
| [0005](0005-publication-git.md) | Publication par file d'envoi vers un dépôt git | 2026-10-21 |
|
||||
| [0006](0006-acces-connect-trustinwallet.md) | Accès : connect.monnaie-libre.fr, invitations, trustinWallet | 2026-10-21 |
|
||||
@@ -0,0 +1,20 @@
|
||||
# Les chantiers
|
||||
|
||||
Un chantier à la fois, **livré complet et en production** avant d'ouvrir le suivant. Chaque
|
||||
chantier a sa fiche (`CHANTIER.md` : ce qu'on construit et pourquoi), ses contrôles d'usage
|
||||
(`CONTROLES.md`) et sa branche commune `chantier/<nom>`.
|
||||
|
||||
| # | Chantier | Outil | Ce qu'il ajoute | État |
|
||||
|---|---|---|---|---|
|
||||
| 1 | [Acte d'engagement Certification](acte-certification/CHANTIER.md) | Docs de référence | le texte sous vote permanent, l'édition distinguée du vote, les contradictions, l'inertie votée, la publication git, l'accès Ğ1 + trustinWallet, le trilingue | **en cours** — binôme Yvv · Chiara |
|
||||
| 2 | Acte d'engagement forgeron | Docs de référence | la **qualification d'une compétence** (au-delà de l'engagement personnel) | à ouvrir après la mise en production du 1 |
|
||||
| 3 | Runtime Upgrade | Protocoles | un **protocole** : décision on-chain d'une mise à jour du runtime Duniter | après le 2 |
|
||||
|
||||
Les outils Décisions et Mandats restent grisés tant qu'aucun chantier ne les ouvre.
|
||||
|
||||
## Ce qu'un chantier doit laisser derrière lui
|
||||
|
||||
- la boucle verte (Rust, interface, e2e) et l'image déployée sur decision.librodrome.org ;
|
||||
- les contrôles d'usage passés par Chiara (ses retours valent contrôles) ;
|
||||
- la fiche à jour : décisions prises, questions ouvertes, ce qui reste ;
|
||||
- les décisions d'architecture durables consignées dans `docs/adr/`.
|
||||
@@ -0,0 +1,154 @@
|
||||
# Chantier 1 — Acte d'engagement Certification
|
||||
|
||||
> Binôme : **Yvv** (porteur) · **Chiara** (testeuse et codeuse — ses tests d'usage valent
|
||||
> contrôles). Branche commune : `chantier/acte-certification`. Ouvert le 2026-10-21.
|
||||
> Production : https://decision.librodrome.org
|
||||
|
||||
## Le besoin, en une phrase
|
||||
|
||||
La communauté Monnaie libre tient son Acte d'engagement Certification comme un **texte vivant** :
|
||||
lisible d'abord, modifiable par article, chaque modification passant par un vote dont le seuil
|
||||
suit la formule inertielle de la Ğ1 — sans qu'une coquille demande un vote, sans que deux
|
||||
articles contradictoires soient en vigueur ensemble, et avec une publication automatique que
|
||||
les applications clientes (Cesium, Ğecko…) peuvent suivre.
|
||||
|
||||
## Ce que voit la personne
|
||||
|
||||
1. **Accueil sobre** : « Rendre possible la décision collective » / « en fonction du contexte,
|
||||
processus et modalités, boîte à outils », les quatre outils en une ligne chacun, puis la
|
||||
connexion. Le menu complet à gauche : Décisions · Docs de référence · Mandats · Protocoles
|
||||
(seul Docs de référence est actif).
|
||||
2. **Connexion Ğ1** — la même que le forum Monnaie libre (connect.monnaie-libre.fr). En attendant
|
||||
l'inscription du client OAuth2, des **liens d'invitation** à usage unique.
|
||||
3. **Groupes** : avec une clé Ğ1, on voit Monnaie libre, Axiom, Librodrome et le Groupe local
|
||||
Vallée Drôme Crestois — pas l'Atelier du canal. Un groupe et son contenu ne s'ouvrent qu'à
|
||||
ses membres ; Monnaie libre s'ouvre à tout membre de la toile de confiance, les autres
|
||||
groupes sur accord dans trustinWallet.
|
||||
4. **Le texte en vigueur**, seul, en grand, typographie de lecture (Literata) : au départ le
|
||||
Préambule et les deux engagements fondamentaux (Unicité du compte, Réciprocité), marqués
|
||||
*seed a minima · 0 vote*. Au survol (ou toujours visibles sur mobile), trois gestes discrets :
|
||||
**Le vote** (en cours, inertie, formule, annexes) · **Les propositions** · **Proposer**. Ils
|
||||
ouvrent un panneau latéral (une feuille sur mobile) dont l'état vit dans l'URL.
|
||||
5. **En dessous**, les propositions en cours — les 30 autres articles du texte source, à zéro
|
||||
vote, inertie standard — rangées par section (introduction, fondamental, technique,
|
||||
conclusion, annexes, formule, inertie, ordonnancement), les variantes signalées.
|
||||
6. **Deux aperçus** : « si les bien cotées passent » et « si tout passe ».
|
||||
7. **Trois langues** : fr · en · es, pour l'interface comme pour le texte. Le français fait foi ;
|
||||
une traduction devenue ancienne (le français a changé depuis) est signalée.
|
||||
|
||||
## Les règles
|
||||
|
||||
### Le seuil — formule inertielle Ğ1
|
||||
|
||||
```
|
||||
R = C + B^W + (M + (1 − M)·(1 − (T/W)^G)) · max(0, T − C)
|
||||
```
|
||||
|
||||
W = électorat (Monnaie libre : taille de la toile, lue sur la chaîne toutes les 6 h ; un groupe
|
||||
géré : ses membres), T = votes exprimés, M = majorité, G = gradient, B = 0,1, **C = plancher de
|
||||
votants « pour »** (Monnaie libre : **50** ; groupe d'essai : 1). Adoptée si « pour » ≥ ⌈R⌉.
|
||||
|
||||
| Inertie | M | G |
|
||||
|---|---|---|
|
||||
| basse | 50 % | 0,1 |
|
||||
| standard (au départ, partout) | 50 % | 0,2 |
|
||||
| haute | 60 % | 0,4 |
|
||||
| maximale | 66 % | 0,6 |
|
||||
|
||||
Moteur : `crates/ld-engine/src/threshold.rs` (testé : 7224 / 120 votants → 94 voix requises).
|
||||
|
||||
### Le vote permanent
|
||||
|
||||
- Toute version d'un article (proposition ou texte en vigueur) se vote à tout moment.
|
||||
- Une proposition **remonte en vigueur** dès qu'elle atteint son seuil **et** tient une marge
|
||||
(pour − requis) plus large que la version en place. La version déplacée redevient une
|
||||
proposition, avec ses votes. Un *seed a minima* a pour marge −C : la première proposition qui
|
||||
atteint son seuil le remplace.
|
||||
- Retirer un article est une proposition comme une autre (« retirer cet article »).
|
||||
- Le balayage tourne à chaque vote et toutes les 5 minutes (la taille de la toile bouge).
|
||||
|
||||
### L'inertie se vote aussi
|
||||
|
||||
Chaque article porte son inertie. La changer est une proposition, **votée elle-même à
|
||||
l'inertie du cran au-dessus** (« inertie + ») : alourdir ou alléger demande plus que modifier.
|
||||
|
||||
### Corriger n'est pas voter
|
||||
|
||||
| Geste | Qui | Effet sur les votes |
|
||||
|---|---|---|
|
||||
| Corriger une **coquille** (accents, casse, ponctuation, espaces ; ou jusqu'à 2 mots à 2 lettres près) | tout membre sur un texte de départ ou en vigueur ; l'auteur sur sa proposition | aucun — les votes restent, les traductions restent à jour |
|
||||
| Modifier **le fond** d'une proposition | son auteur | nouvelle révision : les votes antérieurs sont « à reconfirmer », les signalements tombent |
|
||||
| Modifier le fond d'un texte **en vigueur** ou d'un texte de départ | personne | refusé : on propose une nouvelle version |
|
||||
|
||||
Le classement est prudent : un chiffre ou un mot de négation (ne, pas, jamais, personne…)
|
||||
touché rend la modification « de fond ». Moteur : `crates/ld-engine/src/edit.rs`.
|
||||
|
||||
### Deux articles qui se contredisent
|
||||
|
||||
Tout membre peut **signaler une contradiction** entre deux propositions, motif à l'appui. Tant
|
||||
que le signalement tient, celle des deux qui n'est pas en vigueur **ne peut pas entrer en
|
||||
vigueur**. Il se lève par son auteur (ou un administrateur), ou tombe quand la proposition
|
||||
signalée est révisée sur le fond.
|
||||
|
||||
### Une décision par proposition
|
||||
|
||||
L'éditeur a un **assistant à règles** (pas d'IA pour l'instant ; plus tard, une IA locale, sans
|
||||
abonnement) : il repère un texte qui porte plusieurs décisions et propose de le **découper en
|
||||
brouillons**, signale une proposition très proche d'une existante, et un texte long. Moteur :
|
||||
`crates/ld-engine/src/assist.rs`.
|
||||
|
||||
### Brouillon → proposition
|
||||
|
||||
On écrit en brouillon (libre, invisible des autres), on soumet ; la proposition est alors
|
||||
ouverte au vote. Son auteur peut la retirer.
|
||||
|
||||
### Publication
|
||||
|
||||
Chaque entrée en vigueur (et la publication initiale) passe par une file d'envoi : le serveur
|
||||
écrit `fr.md`, `en.md`, `es.md` (front matter : document, langue, source, mise à jour ; les
|
||||
traductions marquées éditoriales) et `document.json`, commit et pousse sur
|
||||
`git.duniter.org/Yvv/libredecision-textes`, chemin `monnaie-libre/acte-engagement-certification/`.
|
||||
Échec → nouvel essai avec délai croissant. La clé de publication est générée par le serveur ;
|
||||
sa partie publique s'affiche dans trustinWallet, à ajouter comme clé de déploiement (écriture)
|
||||
du dépôt. Le groupe d'essai ne publie jamais.
|
||||
|
||||
### Accès — trustinWallet
|
||||
|
||||
Tout le monde connecté peut tout faire dans les groupes qu'il ouvre (lire, proposer, voter,
|
||||
corriger, signaler) **sauf la gestion des accès**, réservée aux administrateurs de trustinWallet
|
||||
(`/acces`) : membres des groupes, invitations, administrateurs, retours, clé de publication.
|
||||
Le nom trustinWallet remplace trustWallet (déjà pris). Règles : `crates/trustin-access`.
|
||||
|
||||
## Le groupe d'essai
|
||||
|
||||
Caché, plancher C = 1, sans publication : on y éprouve tout (adoption, coquilles, découpage)
|
||||
sans toucher au texte de la communauté. Chiara et Yvv en sont membres.
|
||||
|
||||
## Décisions prises (2026-10-21, avec Yvv)
|
||||
|
||||
| Question | Décision |
|
||||
|---|---|
|
||||
| Stack | Rust (serveur, moteur) + Svelte (interface), pour la durée, la performance, la modularité |
|
||||
| Domaine | le produit à la racine `decision.librodrome.org` ; la maquette sur `maquette.decision.librodrome.org` |
|
||||
| Connexion | client OAuth2 de connect.monnaie-libre.fr (demande à poka) ; liens d'invitation en attendant |
|
||||
| Dépôt des textes | `git.duniter.org/Yvv/libredecision-textes` (compte d'Yvv) |
|
||||
| Garde-fou du vote permanent | nombre minimal de votants « pour » (C) |
|
||||
| Langues | fr, en, es ; le français est voté, les traductions sont éditoriales |
|
||||
| Chiara | testeuse et codeuse |
|
||||
| Assistant | à règles d'abord ; IA locale plus tard, sans dépendance d'abonnement |
|
||||
|
||||
Les raisons longues sont dans `docs/adr/`.
|
||||
|
||||
## Questions ouvertes (à Yvv)
|
||||
|
||||
- **Licence** du code — non choisie.
|
||||
- **Texte « Paramètres par défaut » du seed** (F1.2) : il annonce C = 0 et une durée de 30 jours,
|
||||
alors que le vote est permanent avec C = 50. À reformuler par proposition (c'est leur rôle),
|
||||
ou à corriger dans le seed avant la première publication si c'est une erreur de transcription.
|
||||
- Anglais : l'interface est en anglais américain, le seed en anglais britannique.
|
||||
|
||||
## Ce qui reste après la livraison
|
||||
|
||||
- L'inscription du client OAuth2 (poka) : `DEMANDE-CONNECT.md`, puis les deux secrets dans la CI.
|
||||
- Les retours de Chiara → contrôles → corrections, sur la branche du chantier.
|
||||
- Ensuite : chantier 2, l'Acte d'engagement forgeron (qualification d'une compétence).
|
||||
@@ -0,0 +1,42 @@
|
||||
# Contrôles d'usage — Acte d'engagement Certification
|
||||
|
||||
Chaque contrôle existe en deux versions : **automatique** (Playwright, `web/e2e/`, rejoué par
|
||||
`npm run e2e`) et **humaine** (Chiara, sur la production ou en local). Une remarque de Chiara
|
||||
sur un contrôle vaut échec du contrôle tant qu'elle n'est pas traitée.
|
||||
|
||||
Où tester : les contrôles qui écrivent (C5, C6, C7) se font dans le **groupe d'essai**
|
||||
(caché, plancher C = 1, jamais publié) — jamais sur le texte de la communauté.
|
||||
|
||||
| # | Contrôle | Geste humain | Attendu |
|
||||
|---|---|---|---|
|
||||
| C1 | L'accueil | ouvrir `/` sans être connectée | « Rendre possible la décision collective », la phrase d'outils, les quatre sections ; le menu à gauche avec les quatre outils, trois grisés ; « Se connecter avec sa clé Ğ1 » |
|
||||
| C2 | Les groupes | se connecter, ouvrir « Groupes » | Monnaie libre, Axiom, Librodrome, Groupe local Vallée Drôme Crestois ; **pas** l'Atelier du canal |
|
||||
| C3 | Le texte en vigueur | Monnaie libre → Docs de référence → Acte d'engagement Certification | trois articles seulement (Préambule, Unicité du compte, Réciprocité), chacun « seed a minima · 0 vote » ; en dessous, « Propositions en cours » (32 lignes) |
|
||||
| C4 | Gestes contextuels | survoler le Préambule → « Le vote » | un panneau : seuil 50 voix pour, il en manque 50 ; « La formule du seuil » se déplie ; l'URL garde le panneau ; Échap le ferme |
|
||||
| C5 | Entrée en vigueur | groupe d'essai : à deux, voter « Pour » sur « Les deux garanties réciproques » | au second vote, « … est entré en vigueur » ; le texte en vigueur compte quatre articles |
|
||||
| C6 | Coquille sans vote | groupe d'essai : Préambule → « Le vote » → « Corriger une coquille », remplacer « latransmission » par « la transmission » | « Coquille corrigée, sans toucher aux votes. » ; un changement de sens est refusé : « Ce changement touche au sens : propose-le comme modification. » |
|
||||
| C7 | Une décision par proposition | groupe d'essai : « Proposer un nouvel article », deux paragraphes « Je m'engage à… » | l'assistant : « Ce texte semble porter 2 décisions » ; « Découper en 2 brouillons » ; « Tes brouillons » apparaît |
|
||||
| C8 | Trois langues | choisir EN puis ES pour le texte | « Certification Commitment Act », « Acta de compromiso de Certificación » ; l'interface suit |
|
||||
| C9 | Les aperçus | « Si tout passe », puis « Si les bien cotées passent » | 33 articles avec la mention d'aperçu, puis 3 |
|
||||
| C10 | trustinWallet | ouvrir `/acces` sans être administratrice | « La gestion des accès est réservée aux administrateurs de trustinWallet. » (côté admin : créer un lien d'invitation) |
|
||||
| M1 | Mobile | téléphone (ou fenêtre étroite) | menu en tiroir, les trois gestes visibles sans survol, le panneau s'ouvre en feuille |
|
||||
|
||||
## Au-delà de la liste
|
||||
|
||||
Ce que l'automate ne voit pas et que Chiara regarde :
|
||||
|
||||
- **la lecture** : le texte est-il beau, aéré, sans bruit ? lit-on un article sans effort ?
|
||||
- **la fatigue** : un geste de trop, une étape inutile, un mot qui arrête ;
|
||||
- **le vocabulaire** : un libellé qui parle du système au lieu de parler de toi ou du groupe ;
|
||||
- **les deux ambiances** (pierre / nuit) et le clavier seul (Tab, Entrée, Échap) ;
|
||||
- **les langues** : une traduction maladroite, une chaîne restée en français.
|
||||
|
||||
Chaque remarque passe par « Faire un retour » (menu de gauche) : elle arrive datée, avec la
|
||||
page, la langue et la taille de l'écran. Les retours se traitent dans trustinWallet
|
||||
(Vu → Traité) et, s'ils changent une règle, dans `CHANTIER.md`.
|
||||
|
||||
## Journal des passages
|
||||
|
||||
| Date | Qui | Où | Contrôles | Résultat |
|
||||
|---|---|---|---|---|
|
||||
| 2026-10-21 | automate | local, serveur jetable :8092 | C1–C10, M1 | 11 / 11 verts |
|
||||
@@ -0,0 +1,42 @@
|
||||
# Demande à poka — client OAuth2 sur connect.monnaie-libre.fr
|
||||
|
||||
Brouillon prêt à envoyer (par Yvv, en message privé à poka sur forum.monnaie-libre.fr ou par
|
||||
le canal habituel). Rien de secret dedans ; la réponse, elle, contiendra un secret.
|
||||
|
||||
---
|
||||
|
||||
Bonjour poka,
|
||||
|
||||
On ouvre libreDecision en production sur **https://decision.librodrome.org** : la communauté
|
||||
y tient l'**Acte d'engagement Certification** comme un texte vivant, voté article par article
|
||||
(formule inertielle Ğ1). On voudrait la même connexion que le forum : connect.monnaie-libre.fr.
|
||||
|
||||
Peux-tu inscrire un client OAuth2 ?
|
||||
|
||||
- **Nom affiché** : libreDecision
|
||||
- **Site** : https://decision.librodrome.org
|
||||
- **URI de redirection** : `https://decision.librodrome.org/api/auth/g1/callback`
|
||||
- **Flux** : authorization code (`/oauth/authorize` → `/oauth/token` en basic auth →
|
||||
`/oauth/userinfo`), sans scope particulier
|
||||
- **Ce qu'on lit de `/oauth/userinfo`** : `id` (idty:N), `username`, `address`, `member` —
|
||||
rien d'autre n'est stocké ; seuls les membres de la toile ouvrent le groupe Monnaie libre.
|
||||
|
||||
Il nous faudrait en retour le `client_id` et le `client_secret` (le secret par un canal privé,
|
||||
pas sur le forum).
|
||||
|
||||
Merci !
|
||||
Yvv
|
||||
|
||||
---
|
||||
|
||||
## À la réponse
|
||||
|
||||
1. Ne pas coller le secret dans une conversation : protocole `/tmp/pwd.txt` (l'assistant crée
|
||||
et ouvre le fichier, Yvv colle).
|
||||
2. Ajouter dans Woodpecker (dépôt LPK/decision) deux secrets : `connect_client_id`,
|
||||
`connect_client_secret`.
|
||||
3. **Ensuite seulement** (un `from_secret` absent fait refuser tout le pipeline) : dans
|
||||
`.woodpecker/app.yml`, étape `deploy`, écrire `/opt/libredecision-app/secrets.env` avec
|
||||
`LD_CONNECT_CLIENT_ID` et `LD_CONNECT_CLIENT_SECRET` (chmod 600) — le compose le lit déjà.
|
||||
4. Contrôle : « Se connecter avec sa clé Ğ1 » mène à connect.monnaie-libre.fr et revient
|
||||
connecté ; un membre de la toile ouvre Monnaie libre.
|
||||
@@ -0,0 +1,72 @@
|
||||
# Bienvenue sur le chantier — Chiara
|
||||
|
||||
Tu es **testeuse et codeuse** sur ce chantier : tes tests d'usage sont nos contrôles (une
|
||||
remarque de toi = un contrôle à repasser), et tu peux corriger ou construire toi-même.
|
||||
|
||||
## 1. Ce qu'on construit
|
||||
|
||||
Lis d'abord `CHANTIER.md` (10 minutes) : le texte vivant, le vote permanent, coquille ≠ vote,
|
||||
les contradictions, l'inertie votée, la publication. Puis `CONTROLES.md` : ce que tu vérifies.
|
||||
|
||||
## 2. Tes accès (préparés par Yvv)
|
||||
|
||||
| Quoi | Pour quoi | Qui le fait |
|
||||
|---|---|---|
|
||||
| Un **lien d'invitation** à decision.librodrome.org (groupes Monnaie libre + groupe d'essai) | tester en production avant la connexion Ğ1 | Yvv, dans trustinWallet (`/acces`) |
|
||||
| Ta **clé Ğ1** | une fois connect.monnaie-libre.fr branché, tu te connectes comme sur le forum | — |
|
||||
| Un compte sur **git.open.us.org**, collaboratrice de `yvv/decision` | pousser tes branches, ouvrir des demandes de fusion | Yvv |
|
||||
|
||||
Le lien d'invitation s'ouvre **une seule fois** et expire en 14 jours ; il ouvre ta session sur
|
||||
ce navigateur pour 30 jours.
|
||||
|
||||
## 3. Tester sans rien installer
|
||||
|
||||
1. Ouvre ton lien d'invitation → tu arrives connectée.
|
||||
2. Les contrôles qui écrivent (voter, corriger, découper) se font dans le **groupe d'essai** :
|
||||
il est caché, une seule voix « pour » suffit à faire passer une proposition, et rien n'en
|
||||
sort. Le texte de Monnaie libre, lui, est le vrai : lis-le, ne vote que pour de vrai.
|
||||
3. Passe les contrôles C1–C10 et M1 (sur ton téléphone aussi), puis tout ce qui te gêne.
|
||||
4. Chaque remarque : **« Faire un retour »** dans le menu de gauche. Écris ce que tu as fait, ce
|
||||
que tu attendais, ce qui s'est passé. La page, la langue et la taille de l'écran sont jointes.
|
||||
|
||||
## 4. Coder en local
|
||||
|
||||
Prérequis : git, Node 22, Rust (rustup — la version 1.99 s'installe toute seule grâce à
|
||||
`rust-toolchain.toml`).
|
||||
|
||||
```bash
|
||||
git clone gitea@git.open.us.org:yvv/decision.git libreDecision && cd libreDecision
|
||||
git switch chantier/acte-certification
|
||||
curl --proto '=https' -sSf https://sh.rustup.rs | sh -s -- -y # si Rust n'est pas installé
|
||||
cd web && npm ci && npm run build && cd ..
|
||||
LD_DEV_LOGIN=1 cargo run -p ld-server # http://127.0.0.1:8002 — API + interface
|
||||
# dans un second terminal, pour l'interface à chaud :
|
||||
cd web && npm run dev # http://127.0.0.1:3002
|
||||
```
|
||||
|
||||
`LD_DEV_LOGIN=1` affiche une connexion « développement » : tu tapes un nom, tu es membre de
|
||||
tous les groupes (y compris l'essai). La base locale vit dans `data/` — supprime ce dossier pour
|
||||
repartir du texte de départ.
|
||||
|
||||
Où sont les choses :
|
||||
|
||||
- les **règles** (seuil, coquille, découpage, aperçus) : `crates/ld-engine/` — pur, testé ;
|
||||
- les **accès** : `crates/trustin-access/` ;
|
||||
- l'**API** : `crates/ld-server/src/` (une route = une fonction ; scénarios dans `tests/api.rs`) ;
|
||||
- l'**interface** : `web/src/routes/` (pages) et `web/src/lib/components/` ;
|
||||
- les **textes affichés** : `web/src/lib/i18n/fr.ts` (puis `en.ts`, `es.ts`) — jamais en dur ;
|
||||
- le **texte de départ** : `seeds/acte-engagement-certification.json`.
|
||||
|
||||
## 5. Ta boucle avant de pousser
|
||||
|
||||
```bash
|
||||
cargo fmt --all --check && cargo clippy --workspace --all-targets -- -D warnings && cargo test --workspace
|
||||
cd web && npm run check && npm test && npm run build && npm run e2e
|
||||
```
|
||||
|
||||
Tout vert, puis : `git switch -c chiara/<sujet>`, commits, `git push origin chiara/<sujet>`, et une
|
||||
demande de fusion vers `chantier/acte-certification`. Le détail : `CONTRIBUTING.md`.
|
||||
|
||||
## 6. Si tu bloques
|
||||
|
||||
Écris à Yvv, ou laisse un retour dans l'outil en commençant par « BLOQUÉE : ».
|
||||
@@ -0,0 +1,49 @@
|
||||
# PostgreSQL
|
||||
POSTGRES_DB=libredecision
|
||||
POSTGRES_USER=libredecision
|
||||
POSTGRES_PASSWORD=change-me-in-production
|
||||
DATABASE_URL=postgresql+asyncpg://libredecision:change-me-in-production@localhost:5432/libredecision
|
||||
|
||||
# Backend
|
||||
SECRET_KEY=change-me-in-production-with-a-real-secret-key
|
||||
DEBUG=true
|
||||
|
||||
# Environment: development, staging, production
|
||||
ENVIRONMENT=development
|
||||
|
||||
# Logging: DEBUG, INFO, WARNING, ERROR
|
||||
LOG_LEVEL=INFO
|
||||
|
||||
# CORS
|
||||
CORS_ORIGINS=["http://localhost:3002"]
|
||||
|
||||
# Database connection pool
|
||||
DATABASE_POOL_SIZE=20
|
||||
DATABASE_MAX_OVERFLOW=10
|
||||
|
||||
# Auth / Sessions
|
||||
SESSION_TTL_HOURS=24
|
||||
CHALLENGE_EXPIRE_SECONDS=300
|
||||
|
||||
# Rate limiting (requests per minute)
|
||||
RATE_LIMIT_DEFAULT=60
|
||||
RATE_LIMIT_AUTH=10
|
||||
RATE_LIMIT_VOTE=30
|
||||
|
||||
# Duniter V2 RPC
|
||||
DUNITER_RPC_URL=wss://gdev.p2p.legal/ws
|
||||
DUNITER_RPC_TIMEOUT_SECONDS=10
|
||||
|
||||
# Blockchain cache TTL (seconds)
|
||||
BLOCKCHAIN_CACHE_TTL_SECONDS=3600
|
||||
|
||||
# IPFS
|
||||
IPFS_API_URL=http://localhost:5001
|
||||
IPFS_GATEWAY_URL=http://localhost:8080
|
||||
IPFS_TIMEOUT_SECONDS=30
|
||||
|
||||
# Frontend
|
||||
NUXT_PUBLIC_API_BASE=http://localhost:8002/api/v1
|
||||
|
||||
# Docker / Production
|
||||
DOMAIN=decision.librodrome.org
|
||||
@@ -0,0 +1,118 @@
|
||||
> **Maquette v2** — conservée telle quelle pour présenter le projet, en ligne sur
|
||||
> https://maquette.decision.librodrome.org. Le produit (v3, Rust + Svelte) est à la racine du
|
||||
> dépôt : voir `../README.md`. Les chemins ci-dessous sont relatifs à `maquette/` ; la référence
|
||||
> produit de la maquette reste `../docs/dev/BLUEPRINT-V2.md`.
|
||||
|
||||
# libreDecision
|
||||
|
||||
Boîte à outils de la première démocratie — décider ensemble, du trio à la population.
|
||||
|
||||
libreDecision fait de la décision un geste quotidien : chaque question tient en une phrase,
|
||||
l'outil propose le chemin le plus court — jamais plus lourd que l'enjeu — et tout ce qui est
|
||||
décidé reste révisable, mesuré, consigné. Le même outil sert un trio d'amis, une association,
|
||||
une coopérative ou la toile de confiance Ğ1 (marque blanche par construction).
|
||||
|
||||
## Manifeste
|
||||
|
||||
> Personne n'a jamais vécu en démocratie — alors nous la rêvons de jour, et nous la commençons
|
||||
> à notre échelle. libreDecision attrape chaque décision en une phrase et propose le chemin le
|
||||
> plus court : décide seul et trace-le, décide sous mandat, écoute puis décide, ou décidez
|
||||
> ensemble — jamais plus lourd que l'enjeu. Ici on ne délègue pas, on mandate, et on reprend ;
|
||||
> on n'est pas consulté, on s'instruit, puis on se prononce ; on ne s'affronte pas en deux
|
||||
> camps, on règle des curseurs — et la position de chacun reste visible dans le faisceau, la
|
||||
> médiane n'écrase personne, la dernière main est toujours humaine. Les concernés en premier
|
||||
> lieu ont voix par construction, chacun voit pourquoi ; lever la main « ça me concerne » est
|
||||
> un droit, pas une demande. Chaque décision dit ce qu'elle engage, ce qu'elle cherche à
|
||||
> changer, et repasse l'épreuve du réel : rien n'est définitif, toute décision en vigueur garde
|
||||
> sa poignée, se tromper est une donnée qu'on consigne. Les règles du jeu elles-mêmes — le
|
||||
> Pacte — s'écrivent au fil de l'eau, par ceux qu'elles gouvernent, sous les boussoles
|
||||
> Autonomie, Équilibre, Liaison. Du trio d'amis à la toile Ğ1 : le même geste, dix secondes,
|
||||
> et l'observatoire qui apprend de nous. La ville change, le puits demeure.
|
||||
|
||||
## Les 6 routes de décision
|
||||
|
||||
Le triage (moteur pur, toujours expliqué en une phrase, toujours contournable) oriente chaque
|
||||
décision vers l'une de six routes :
|
||||
|
||||
| Route | Geste |
|
||||
|---|---|
|
||||
| **solo** | « Personne d'autre n'est concerné — décide. » 2 gestes, moins de 10 secondes, trace facultative |
|
||||
| **record** | « Déjà tranché — je le consigne. » L'observatoire en un geste, adopté immédiatement |
|
||||
| **mandate** | « Ton mandat couvre — décide, c'est tracé. » Trace obligatoire + fenêtre d'objection 48 h |
|
||||
| **transmit** | « Le mandat d'un autre couvre — transmets-lui. » (ou conteste le mandat) |
|
||||
| **advice** | « Réversible et petit cercle — demande leur avis, puis décide. » 72 h |
|
||||
| **collective** | « Nous décidons. » Formulation, liste des concernés arrêtée, session de vote |
|
||||
|
||||
Dérogation asymétrique : alourdir est libre (1 tap journalisé) ; alléger exige une note motivée
|
||||
et une fenêtre d'objection.
|
||||
|
||||
## Les 5 modalités
|
||||
|
||||
Chaque protocole est nommé et référencé par une clause du Pacte — le modifier, c'est amender
|
||||
le Pacte.
|
||||
|
||||
1. **Consentement** — « Ça me va / J'objecte » : zéro objection maintenue = adopté. Le repli
|
||||
universel : tout collectif en a un (invariant).
|
||||
2. **Nuancé 6 niveaux** — de « Pas du tout » à « Tout à fait », jamais « contre » ;
|
||||
histogramme visible, commentaire obligatoire sur les niveaux bas.
|
||||
3. **Paramétrique** — décider au curseur : faisceau des positions, médiane vivante, carte
|
||||
« Pour moi » ; la cristallisation finale est un geste humain daté du garant, jamais un
|
||||
automatisme.
|
||||
4. **Élection** — réservée aux mandats et aux problématiques d'échelle (6 méthodes de
|
||||
nomination, vote blanc, jamais de départage par l'outil).
|
||||
5. **Binaire inertiel** — le seuil `C + B^W + (M + (1−M)(1−(T/W)^G))·max(0, T−C)`, héritage
|
||||
Ğ1 remis à sa place : l'outil de dernier recours des très grands corps. L'Atelier des
|
||||
formules (`/textes/formules`) reste son école.
|
||||
|
||||
## Local-first
|
||||
|
||||
- **Aucun serveur requis** : tout l'état vit dans le navigateur (IndexedDB via idb-keyval,
|
||||
une clé par collectif).
|
||||
- **Export / import** de bundles JSON — la fédération, c'est l'échange de bundles, jamais une
|
||||
table.
|
||||
- **Seeds** livrés et importés par le même chemin que l'import utilisateur :
|
||||
- `duniter-g1.bundle.json` — la toile Ğ1 : 33 clauses Certification + 59 clauses Forgeron
|
||||
(textes votés intacts, provenance et décomptes conservés), protocoles, 3 mandats ;
|
||||
- `atelier-du-canal.bundle.json` — association fictive de 12 membres, le test permanent de
|
||||
marque blanche : décisions à tous les états, paramétrique vivant, épreuve du réel due.
|
||||
- **7 gabarits** de collectif à la création (`/creer`) : page blanche, informel, association,
|
||||
coopérative, commune, monnaie libre, institution symétrique.
|
||||
|
||||
## Stack
|
||||
|
||||
- **Frontend** : Nuxt 4 (SPA, SSR off) + Vue 3 + TypeScript + Nuxt UI v3 + Pinia + UnoCSS ;
|
||||
npm ; port **3002**
|
||||
- **Moteurs purs** : `frontend/app/engine/` — triage, machine à états, seuils, médiane
|
||||
paramétrique, nuancé, périmètres — zéro dépendance Nuxt, testés vitest (**342 tests**)
|
||||
- **Backend v1** (FastAPI + SQLAlchemy async) : conservé dans `backend/` pour la future
|
||||
synchronisation multi-appareils — non requis pour utiliser ou développer le frontend
|
||||
|
||||
## Démarrage
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
npm install
|
||||
npm run dev # http://localhost:3002
|
||||
npm test # vitest — 342 tests
|
||||
npm run build # build de production
|
||||
```
|
||||
|
||||
## Structure v2
|
||||
|
||||
```
|
||||
frontend/app/
|
||||
engine/ # moteurs purs (barrel index.ts — seul point d'import)
|
||||
data/ # persistence.ts (IndexedDB), templates.ts (7 gabarits), seeds/*.bundle.json
|
||||
stores/ # collective.ts (le tenant) + decisions.ts (façade d'actions)
|
||||
composables/ # useMood, useFeed, useSearch
|
||||
components/ # par domaine : chemin/ decisions/ votes/ texts/ mandates/ feed/ onboarding/ common/
|
||||
pages/ # 14 routes : / decider decisions(+id, vote, observatoire)
|
||||
# textes(+slug, formules) mandats(+id, nouveau) creer donnees
|
||||
lexicon.ts # source unique des libellés FR + test anti-lexique
|
||||
types/domain.ts # le noyau du modèle
|
||||
docs/dev/BLUEPRINT-V2.md # la référence produit
|
||||
```
|
||||
|
||||
## Licence
|
||||
|
||||
Logiciel libre. Le fichier LICENSE sera posé avant la première publication.
|
||||
Reference in New Issue
Block a user