diff --git a/backend/app/config.py b/backend/app/config.py
index 731de26..942956b 100644
--- a/backend/app/config.py
+++ b/backend/app/config.py
@@ -10,8 +10,8 @@ class Settings(BaseSettings):
ENVIRONMENT: str = "development" # development, staging, production
LOG_LEVEL: str = "INFO"
- # Database
- DATABASE_URL: str = "postgresql+asyncpg://glibredecision:change-me-in-production@localhost:5432/glibredecision"
+ # Database — SQLite by default for local dev, PostgreSQL for Docker/prod
+ DATABASE_URL: str = "sqlite+aiosqlite:///./glibredecision.db"
DATABASE_POOL_SIZE: int = 20
DATABASE_MAX_OVERFLOW: int = 10
diff --git a/backend/app/database.py b/backend/app/database.py
index e83e36a..c38787a 100644
--- a/backend/app/database.py
+++ b/backend/app/database.py
@@ -3,14 +3,25 @@ from sqlalchemy.orm import DeclarativeBase
from app.config import settings
-engine = create_async_engine(
- settings.DATABASE_URL,
- echo=settings.ENVIRONMENT == "development",
- pool_size=settings.DATABASE_POOL_SIZE,
- max_overflow=settings.DATABASE_MAX_OVERFLOW,
- pool_pre_ping=True,
- pool_recycle=3600,
-)
+_is_sqlite = settings.DATABASE_URL.startswith("sqlite")
+
+# SQLite doesn't support pool_size/max_overflow/pool_pre_ping/pool_recycle
+if _is_sqlite:
+ engine = create_async_engine(
+ settings.DATABASE_URL,
+ echo=settings.ENVIRONMENT == "development",
+ connect_args={"check_same_thread": False},
+ )
+else:
+ engine = create_async_engine(
+ settings.DATABASE_URL,
+ echo=settings.ENVIRONMENT == "development",
+ pool_size=settings.DATABASE_POOL_SIZE,
+ max_overflow=settings.DATABASE_MAX_OVERFLOW,
+ pool_pre_ping=True,
+ pool_recycle=3600,
+ )
+
async_session = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
diff --git a/backend/app/models/cache.py b/backend/app/models/cache.py
index 99f21ef..95c9f4e 100644
--- a/backend/app/models/cache.py
+++ b/backend/app/models/cache.py
@@ -1,8 +1,7 @@
import uuid
from datetime import datetime
-from sqlalchemy import String, DateTime, func
-from sqlalchemy.dialects.postgresql import UUID, JSONB
+from sqlalchemy import String, DateTime, JSON, Uuid, func
from sqlalchemy.orm import Mapped, mapped_column
from app.database import Base
@@ -11,8 +10,8 @@ from app.database import Base
class BlockchainCache(Base):
__tablename__ = "blockchain_cache"
- id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
+ id: Mapped[uuid.UUID] = mapped_column(Uuid, primary_key=True, default=uuid.uuid4)
cache_key: Mapped[str] = mapped_column(String(256), unique=True, nullable=False, index=True)
- cache_value: Mapped[dict] = mapped_column(JSONB, nullable=False)
+ cache_value: Mapped[dict] = mapped_column(JSON, nullable=False)
fetched_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
diff --git a/backend/app/models/decision.py b/backend/app/models/decision.py
index 619f41d..282a68d 100644
--- a/backend/app/models/decision.py
+++ b/backend/app/models/decision.py
@@ -1,8 +1,7 @@
import uuid
from datetime import datetime
-from sqlalchemy import String, Integer, Text, DateTime, ForeignKey, func
-from sqlalchemy.dialects.postgresql import UUID
+from sqlalchemy import String, Integer, Text, DateTime, ForeignKey, Uuid, func
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.database import Base
@@ -11,7 +10,7 @@ from app.database import Base
class Decision(Base):
__tablename__ = "decisions"
- id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
+ id: Mapped[uuid.UUID] = mapped_column(Uuid, primary_key=True, default=uuid.uuid4)
title: Mapped[str] = mapped_column(String(256), nullable=False)
description: Mapped[str | None] = mapped_column(Text)
context: Mapped[str | None] = mapped_column(Text)
@@ -28,7 +27,7 @@ class Decision(Base):
class DecisionStep(Base):
__tablename__ = "decision_steps"
- id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
+ id: Mapped[uuid.UUID] = mapped_column(Uuid, primary_key=True, default=uuid.uuid4)
decision_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("decisions.id"), nullable=False)
step_order: Mapped[int] = mapped_column(Integer, nullable=False)
step_type: Mapped[str] = mapped_column(String(32), nullable=False) # qualification, review, vote, execution, reporting
diff --git a/backend/app/models/document.py b/backend/app/models/document.py
index 20709ab..adf7280 100644
--- a/backend/app/models/document.py
+++ b/backend/app/models/document.py
@@ -1,8 +1,7 @@
import uuid
from datetime import datetime
-from sqlalchemy import String, Integer, Text, DateTime, ForeignKey, func
-from sqlalchemy.dialects.postgresql import UUID
+from sqlalchemy import String, Integer, Text, DateTime, ForeignKey, Uuid, func
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.database import Base
@@ -11,7 +10,7 @@ from app.database import Base
class Document(Base):
__tablename__ = "documents"
- id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
+ id: Mapped[uuid.UUID] = mapped_column(Uuid, primary_key=True, default=uuid.uuid4)
slug: Mapped[str] = mapped_column(String(128), unique=True, nullable=False, index=True)
title: Mapped[str] = mapped_column(String(256), nullable=False)
doc_type: Mapped[str] = mapped_column(String(64), nullable=False) # licence, engagement, reglement, constitution
@@ -29,7 +28,7 @@ class Document(Base):
class DocumentItem(Base):
__tablename__ = "document_items"
- id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
+ id: Mapped[uuid.UUID] = mapped_column(Uuid, primary_key=True, default=uuid.uuid4)
document_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("documents.id"), nullable=False)
position: Mapped[str] = mapped_column(String(16), nullable=False) # "1", "1.1", "3.2"
item_type: Mapped[str] = mapped_column(String(32), default="clause") # clause, rule, verification, preamble, section
@@ -47,7 +46,7 @@ class DocumentItem(Base):
class ItemVersion(Base):
__tablename__ = "item_versions"
- id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
+ id: Mapped[uuid.UUID] = mapped_column(Uuid, primary_key=True, default=uuid.uuid4)
item_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("document_items.id"), nullable=False)
proposed_text: Mapped[str] = mapped_column(Text, nullable=False)
diff_text: Mapped[str | None] = mapped_column(Text)
diff --git a/backend/app/models/mandate.py b/backend/app/models/mandate.py
index 71a9712..d231fd3 100644
--- a/backend/app/models/mandate.py
+++ b/backend/app/models/mandate.py
@@ -1,8 +1,7 @@
import uuid
from datetime import datetime
-from sqlalchemy import String, Integer, Text, DateTime, ForeignKey, func
-from sqlalchemy.dialects.postgresql import UUID
+from sqlalchemy import String, Integer, Text, DateTime, ForeignKey, Uuid, func
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.database import Base
@@ -11,7 +10,7 @@ from app.database import Base
class Mandate(Base):
__tablename__ = "mandates"
- id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
+ id: Mapped[uuid.UUID] = mapped_column(Uuid, primary_key=True, default=uuid.uuid4)
title: Mapped[str] = mapped_column(String(256), nullable=False)
description: Mapped[str | None] = mapped_column(Text)
mandate_type: Mapped[str] = mapped_column(String(64), nullable=False) # techcomm, smith, custom
@@ -29,7 +28,7 @@ class Mandate(Base):
class MandateStep(Base):
__tablename__ = "mandate_steps"
- id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
+ id: Mapped[uuid.UUID] = mapped_column(Uuid, primary_key=True, default=uuid.uuid4)
mandate_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("mandates.id"), nullable=False)
step_order: Mapped[int] = mapped_column(Integer, nullable=False)
step_type: Mapped[str] = mapped_column(String(32), nullable=False) # formulation, candidacy, vote, assignment, reporting, completion, revocation
diff --git a/backend/app/models/protocol.py b/backend/app/models/protocol.py
index 8757ef8..8bb5474 100644
--- a/backend/app/models/protocol.py
+++ b/backend/app/models/protocol.py
@@ -1,8 +1,7 @@
import uuid
from datetime import datetime
-from sqlalchemy import String, Integer, Float, Boolean, DateTime, ForeignKey, Text, func
-from sqlalchemy.dialects.postgresql import UUID
+from sqlalchemy import String, Integer, Float, Boolean, DateTime, ForeignKey, Text, Uuid, func
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.database import Base
@@ -11,7 +10,7 @@ from app.database import Base
class FormulaConfig(Base):
__tablename__ = "formula_configs"
- id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
+ id: Mapped[uuid.UUID] = mapped_column(Uuid, primary_key=True, default=uuid.uuid4)
name: Mapped[str] = mapped_column(String(128), nullable=False)
description: Mapped[str | None] = mapped_column(Text)
@@ -40,7 +39,7 @@ class FormulaConfig(Base):
class VotingProtocol(Base):
__tablename__ = "voting_protocols"
- id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
+ id: Mapped[uuid.UUID] = mapped_column(Uuid, primary_key=True, default=uuid.uuid4)
name: Mapped[str] = mapped_column(String(128), nullable=False)
description: Mapped[str | None] = mapped_column(Text)
vote_type: Mapped[str] = mapped_column(String(32), nullable=False) # binary, nuanced
diff --git a/backend/app/models/sanctuary.py b/backend/app/models/sanctuary.py
index cd4809c..0baadd9 100644
--- a/backend/app/models/sanctuary.py
+++ b/backend/app/models/sanctuary.py
@@ -1,8 +1,7 @@
import uuid
from datetime import datetime
-from sqlalchemy import String, Integer, Text, DateTime, func
-from sqlalchemy.dialects.postgresql import UUID
+from sqlalchemy import String, Integer, Text, DateTime, Uuid, func
from sqlalchemy.orm import Mapped, mapped_column
from app.database import Base
@@ -11,9 +10,9 @@ from app.database import Base
class SanctuaryEntry(Base):
__tablename__ = "sanctuary_entries"
- id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
+ id: Mapped[uuid.UUID] = mapped_column(Uuid, primary_key=True, default=uuid.uuid4)
entry_type: Mapped[str] = mapped_column(String(64), nullable=False) # document, decision, vote_result
- reference_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), nullable=False)
+ reference_id: Mapped[uuid.UUID] = mapped_column(Uuid, nullable=False)
title: Mapped[str | None] = mapped_column(String(256))
content_hash: Mapped[str] = mapped_column(String(128), nullable=False) # SHA-256
ipfs_cid: Mapped[str | None] = mapped_column(String(128))
diff --git a/backend/app/models/user.py b/backend/app/models/user.py
index 0dfb802..e19a7c0 100644
--- a/backend/app/models/user.py
+++ b/backend/app/models/user.py
@@ -1,8 +1,7 @@
import uuid
from datetime import datetime
-from sqlalchemy import String, Boolean, DateTime, ForeignKey, func
-from sqlalchemy.dialects.postgresql import UUID
+from sqlalchemy import String, Boolean, DateTime, ForeignKey, Uuid, func
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.database import Base
@@ -11,7 +10,7 @@ from app.database import Base
class DuniterIdentity(Base):
__tablename__ = "duniter_identities"
- id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
+ id: Mapped[uuid.UUID] = mapped_column(Uuid, primary_key=True, default=uuid.uuid4)
address: Mapped[str] = mapped_column(String(64), unique=True, nullable=False, index=True)
display_name: Mapped[str | None] = mapped_column(String(128))
wot_status: Mapped[str] = mapped_column(String(32), default="unknown") # member, pending, revoked, unknown
@@ -26,7 +25,7 @@ class DuniterIdentity(Base):
class Session(Base):
__tablename__ = "sessions"
- id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
+ id: Mapped[uuid.UUID] = mapped_column(Uuid, primary_key=True, default=uuid.uuid4)
token_hash: Mapped[str] = mapped_column(String(128), unique=True, nullable=False, index=True)
identity_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("duniter_identities.id"), nullable=False)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
diff --git a/backend/app/models/vote.py b/backend/app/models/vote.py
index b2950b8..bef610d 100644
--- a/backend/app/models/vote.py
+++ b/backend/app/models/vote.py
@@ -1,8 +1,7 @@
import uuid
from datetime import datetime
-from sqlalchemy import String, Integer, Float, Boolean, Text, DateTime, ForeignKey, func
-from sqlalchemy.dialects.postgresql import UUID
+from sqlalchemy import String, Integer, Float, Boolean, Text, DateTime, ForeignKey, Uuid, func
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.database import Base
@@ -11,7 +10,7 @@ from app.database import Base
class VoteSession(Base):
__tablename__ = "vote_sessions"
- id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
+ id: Mapped[uuid.UUID] = mapped_column(Uuid, primary_key=True, default=uuid.uuid4)
decision_id: Mapped[uuid.UUID | None] = mapped_column(ForeignKey("decisions.id"))
item_version_id: Mapped[uuid.UUID | None] = mapped_column(ForeignKey("item_versions.id"))
voting_protocol_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("voting_protocols.id"), nullable=False)
@@ -49,7 +48,7 @@ class VoteSession(Base):
class Vote(Base):
__tablename__ = "votes"
- id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
+ id: Mapped[uuid.UUID] = mapped_column(Uuid, primary_key=True, default=uuid.uuid4)
session_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("vote_sessions.id"), nullable=False)
voter_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("duniter_identities.id"), nullable=False)
vote_value: Mapped[str] = mapped_column(String(32), nullable=False) # for, against, or nuanced levels
diff --git a/backend/app/routers/auth.py b/backend/app/routers/auth.py
index 56b822d..3549eec 100644
--- a/backend/app/routers/auth.py
+++ b/backend/app/routers/auth.py
@@ -5,7 +5,7 @@ from __future__ import annotations
import secrets
from datetime import datetime, timedelta, timezone
-from fastapi import APIRouter, Depends, HTTPException, status
+from fastapi import APIRouter, Depends, HTTPException, Response, status
from sqlalchemy.ext.asyncio import AsyncSession
from app.config import settings
@@ -133,11 +133,11 @@ async def get_me(
return IdentityOut.model_validate(identity)
-@router.post("/logout", status_code=status.HTTP_204_NO_CONTENT)
+@router.post("/logout", status_code=status.HTTP_204_NO_CONTENT, response_class=Response, response_model=None)
async def logout(
db: AsyncSession = Depends(get_db),
identity: DuniterIdentity = Depends(get_current_identity),
-) -> None:
+):
"""Invalidate the current session token.
Note: get_current_identity already validated the token, so we know it exists.
diff --git a/backend/app/routers/decisions.py b/backend/app/routers/decisions.py
index a1b5ace..e4ece47 100644
--- a/backend/app/routers/decisions.py
+++ b/backend/app/routers/decisions.py
@@ -4,7 +4,7 @@ from __future__ import annotations
import uuid
-from fastapi import APIRouter, Depends, HTTPException, Query, status
+from fastapi import APIRouter, Depends, HTTPException, Query, Response, status
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
@@ -188,7 +188,7 @@ async def create_vote_session_for_step_endpoint(
return VoteSessionOut.model_validate(session)
-@router.delete("/{id}", status_code=status.HTTP_204_NO_CONTENT)
+@router.delete("/{id}", status_code=status.HTTP_204_NO_CONTENT, response_class=Response, response_model=None)
async def delete_decision(
id: uuid.UUID,
db: AsyncSession = Depends(get_db),
diff --git a/backend/app/routers/documents.py b/backend/app/routers/documents.py
index c6d068c..a4c516b 100644
--- a/backend/app/routers/documents.py
+++ b/backend/app/routers/documents.py
@@ -6,7 +6,7 @@ import difflib
import logging
import uuid
-from fastapi import APIRouter, Depends, HTTPException, Query, status
+from fastapi import APIRouter, Depends, HTTPException, Query, Response, status
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession
@@ -316,7 +316,7 @@ async def update_item(
return DocumentItemOut.model_validate(item)
-@router.delete("/{slug}/items/{item_id}", status_code=status.HTTP_204_NO_CONTENT)
+@router.delete("/{slug}/items/{item_id}", status_code=status.HTTP_204_NO_CONTENT, response_class=Response, response_model=None)
async def delete_item(
slug: str,
item_id: uuid.UUID,
diff --git a/backend/app/routers/mandates.py b/backend/app/routers/mandates.py
index 6a705de..32d7191 100644
--- a/backend/app/routers/mandates.py
+++ b/backend/app/routers/mandates.py
@@ -4,7 +4,7 @@ from __future__ import annotations
import uuid
-from fastapi import APIRouter, Depends, HTTPException, Query, status
+from fastapi import APIRouter, Depends, HTTPException, Query, Response, status
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
@@ -124,7 +124,7 @@ async def update_mandate(
return MandateOut.model_validate(mandate)
-@router.delete("/{id}", status_code=status.HTTP_204_NO_CONTENT)
+@router.delete("/{id}", status_code=status.HTTP_204_NO_CONTENT, response_class=Response, response_model=None)
async def delete_mandate(
id: uuid.UUID,
db: AsyncSession = Depends(get_db),
diff --git a/backend/requirements.txt b/backend/requirements.txt
index 8b1a9ca..8401843 100644
--- a/backend/requirements.txt
+++ b/backend/requirements.txt
@@ -3,6 +3,7 @@ uvicorn[standard]==0.34.0
sqlalchemy==2.0.36
alembic==1.14.0
asyncpg==0.30.0
+aiosqlite==0.22.1
pydantic==2.10.3
pydantic-settings==2.7.0
python-multipart==0.0.18
diff --git a/backend/seed.py b/backend/seed.py
index d79e0b4..cfe7712 100644
--- a/backend/seed.py
+++ b/backend/seed.py
@@ -101,39 +101,45 @@ async def seed_formula_configs(session: AsyncSession) -> dict[str, FormulaConfig
# ---------------------------------------------------------------------------
-# Seed: VotingProtocols
+# Seed: VotingProtocols (premier pack de modalites)
# ---------------------------------------------------------------------------
async def seed_voting_protocols(
session: AsyncSession,
formulas: dict[str, FormulaConfig],
) -> dict[str, VotingProtocol]:
- """Create the 4 base voting protocols."""
+ """Create the first pack of voting modalities (3 protocols)."""
protocols: dict[str, dict] = {
- "Standard G1": {
- "description": "Protocole binaire standard pour la Licence G1.",
+ "Vote majoritaire": {
+ "description": (
+ "Vote binaire a majorite simple. Le seuil d'adoption "
+ "s'adapte dynamiquement au taux de participation via "
+ "la formule d'inertie WoT."
+ ),
"vote_type": "binary",
"formula_config_id": formulas["Standard Licence G1"].id,
"mode_params": "D30M50B.1G.2",
},
- "Forgeron Smith": {
- "description": "Protocole binaire avec critere Smith pour les forgerons.",
+ "Vote quadratique": {
+ "description": (
+ "Vote pondere par la racine carree des certifications. "
+ "Reduit l'influence des gros certificateurs et favorise "
+ "une participation large et diversifiee."
+ ),
"vote_type": "binary",
"formula_config_id": formulas["Forgeron avec Smith"].id,
"mode_params": "D30M50B.1G.2S.1",
},
- "Comite Tech": {
- "description": "Protocole binaire avec critere Comite Technique.",
+ "Vote permanent": {
+ "description": (
+ "Vote continu sans date de fin. Le resultat evolue en "
+ "temps reel avec chaque nouveau vote. Adapte aux documents "
+ "de reference sous revision permanente."
+ ),
"vote_type": "binary",
"formula_config_id": formulas["Comite Tech"].id,
"mode_params": "D30M50B.1G.2T.1",
},
- "Vote Nuance 6 niveaux": {
- "description": "Protocole de vote nuance a 6 niveaux.",
- "vote_type": "nuanced",
- "formula_config_id": formulas["Vote Nuance"].id,
- "mode_params": None,
- },
}
result: dict[str, VotingProtocol] = {}
@@ -149,143 +155,134 @@ async def seed_voting_protocols(
# ---------------------------------------------------------------------------
-# Seed: Document - Licence G1
+# Seed: Document - Acte d'engagement certification
# ---------------------------------------------------------------------------
-LICENCE_G1_ITEMS: list[dict] = [
+ENGAGEMENT_CERTIFICATION_ITEMS: list[dict] = [
{
"position": "1",
"item_type": "preamble",
- "title": "Preambule",
+ "title": "Objet",
"sort_order": 1,
"current_text": (
- "Licence de la monnaie libre et engagement de responsabilite. "
- "La monnaie libre G1 (June) est co-produite par ses membres."
+ "Le present acte definit les engagements de tout membre de la "
+ "toile de confiance qui certifie l'identite d'une autre personne "
+ "dans le reseau Duniter."
),
},
{
"position": "2",
- "item_type": "section",
- "title": "Avertissement TdC",
+ "item_type": "clause",
+ "title": "Connaissance personnelle",
"sort_order": 2,
"current_text": (
- "Certifier n'est pas uniquement s'assurer de l'identite unique "
- "de la personne (son unicite). C'est aussi affirmer que vous la "
- "connaissez bien et que vous saurez la joindre facilement."
+ "Je certifie connaitre personnellement la personne que je "
+ "certifie, l'avoir rencontree physiquement a plusieurs reprises, "
+ "et pouvoir la contacter par au moins deux moyens de communication "
+ "differents."
),
},
{
"position": "3",
"item_type": "clause",
- "title": "Conseils",
+ "title": "Verification d'identite",
"sort_order": 3,
"current_text": (
- "Connaitre la personne par plusieurs moyens de communication differents "
- "(physique, electronique, etc.). Connaitre son lieu de vie principal. "
- "Avoir echange avec elle en utilisant des moyens de communication "
- "susceptibles d'identifier un humain vivant."
+ "Je certifie avoir verifie que la personne n'a qu'un seul compte "
+ "membre dans la toile de confiance, et que l'identite declaree "
+ "correspond a une personne humaine vivante."
),
},
{
"position": "4",
- "item_type": "verification",
- "title": "Verifications",
+ "item_type": "clause",
+ "title": "Engagement de suivi",
"sort_order": 4,
"current_text": (
- "De suffisamment bien connaitre la personne pour pouvoir la contacter, "
- "echanger avec elle. De s'assurer que la personne a bien le controle "
- "de son compte Duniter."
+ "Je m'engage a surveiller l'activite de mes certifies et a "
+ "signaler tout comportement suspect (comptes multiples, "
+ "usurpation d'identite, comptes abandonnes)."
),
},
{
"position": "5",
- "item_type": "rule",
- "title": "Regles TdC",
+ "item_type": "verification",
+ "title": "Delai entre certifications",
"sort_order": 5,
"current_text": (
- "Chaque membre dispose de 100 certifications possibles. "
- "Il est possible de certifier 1 nouveau membre tous les 5 jours. "
- "Un membre doit avoir au moins 5 certifications pour devenir membre. "
- "Un membre doit renouveler son adhesion tous les 2 ans."
+ "Je respecte un delai minimum de reflexion de 5 jours entre "
+ "chaque nouvelle certification emise."
),
},
{
"position": "6",
"item_type": "rule",
- "title": "Production DU",
+ "title": "Renouvellement",
"sort_order": 6,
"current_text": (
- "1 DU (Dividende Universel) est produit par personne et par jour. "
- "Le DU est la monnaie de base co-produite par chaque membre."
+ "Je renouvelle mes certifications avant leur expiration pour "
+ "maintenir la cohesion de la toile de confiance."
),
},
{
"position": "7",
- "item_type": "rule",
- "title": "Code monetaire",
+ "item_type": "clause",
+ "title": "Responsabilite",
"sort_order": 7,
"current_text": (
- "DU formule : DU(t+1) = DU(t) + c^2 * M/N. "
- "c = 4.88% / an. Le DU est re-evalue chaque equinoxe."
+ "Je suis conscient que la certification engage ma responsabilite "
+ "vis-a-vis de la communaute. Une certification abusive peut "
+ "entrainer la perte de confiance des autres membres."
),
},
{
"position": "8",
- "item_type": "clause",
- "title": "Logiciels",
+ "item_type": "rule",
+ "title": "Revocation",
"sort_order": 8,
"current_text": (
- "Les logiciels G1 doivent transmettre cette licence integralement "
- "aux utilisateurs et developper un acces libre au code source."
- ),
- },
- {
- "position": "9",
- "item_type": "clause",
- "title": "Modification",
- "sort_order": 9,
- "current_text": (
- "Proposants, soutiens et votants doivent etre membres de la TdC. "
- "Toute modification de cette licence doit etre soumise au vote "
- "des membres selon le protocole en vigueur."
+ "Une certification peut etre revoquee si les conditions de "
+ "l'engagement ne sont plus remplies. La revocation est soumise "
+ "au protocole de vote en vigueur."
),
},
]
-async def seed_document_licence_g1(session: AsyncSession) -> Document:
- """Create the Licence G1 document with its items."""
+async def seed_document_engagement_certification(session: AsyncSession) -> Document:
+ """Create the Acte d'engagement certification document with its items."""
doc, created = await get_or_create(
session,
Document,
"slug",
- "licence-g1",
- title="Licence G1",
- doc_type="licence",
- version="0.3.0",
+ "engagement-certification",
+ title="Acte d'engagement certification",
+ doc_type="engagement",
+ version="1.0.0",
status="active",
description=(
- "Licence de la monnaie libre G1 (June). "
- "Definit les regles de la toile de confiance et du Dividende Universel."
+ "Acte d'engagement pour les certificateurs de la toile de confiance "
+ "Duniter. Definit les obligations et responsabilites liees a la "
+ "certification de nouveaux membres."
),
)
- print(f" Document 'Licence G1': {'created' if created else 'exists'}")
+ print(f" Document 'Acte d'engagement certification': {'created' if created else 'exists'}")
if created:
- for item_data in LICENCE_G1_ITEMS:
+ for item_data in ENGAGEMENT_CERTIFICATION_ITEMS:
item = DocumentItem(document_id=doc.id, **item_data)
session.add(item)
await session.flush()
- print(f" -> {len(LICENCE_G1_ITEMS)} items created")
+ print(f" -> {len(ENGAGEMENT_CERTIFICATION_ITEMS)} items created")
return doc
# ---------------------------------------------------------------------------
-# Seed: Document - Engagement Forgeron v2.0.0
+# Seed: Document - Acte d'engagement forgeron v2.0.0
# ---------------------------------------------------------------------------
-FORGERON_ITEMS: list[dict] = [
+ENGAGEMENT_FORGERON_ITEMS: list[dict] = [
{
"position": "1",
"item_type": "preamble",
@@ -387,36 +384,36 @@ FORGERON_ITEMS: list[dict] = [
]
-async def seed_document_forgeron(session: AsyncSession) -> Document:
- """Create the Engagement Forgeron v2.0.0 document with its items."""
+async def seed_document_engagement_forgeron(session: AsyncSession) -> Document:
+ """Create the Acte d'engagement forgeron v2.0.0 document with its items."""
doc, created = await get_or_create(
session,
Document,
"slug",
"engagement-forgeron",
- title="Engagement Forgeron v2.0.0",
+ title="Acte d'engagement forgeron",
doc_type="engagement",
version="2.0.0",
status="active",
description=(
- "Engagement des forgerons (validateurs) pour Duniter V2. "
- "Adopte en fevrier 2026 (97 pour / 23 contre)."
+ "Acte d'engagement des forgerons (validateurs de blocs) pour "
+ "Duniter V2. Adopte en fevrier 2026 (97 pour / 23 contre)."
),
)
- print(f" Document 'Engagement Forgeron v2.0.0': {'created' if created else 'exists'}")
+ print(f" Document 'Acte d'engagement forgeron': {'created' if created else 'exists'}")
if created:
- for item_data in FORGERON_ITEMS:
+ for item_data in ENGAGEMENT_FORGERON_ITEMS:
item = DocumentItem(document_id=doc.id, **item_data)
session.add(item)
await session.flush()
- print(f" -> {len(FORGERON_ITEMS)} items created")
+ print(f" -> {len(ENGAGEMENT_FORGERON_ITEMS)} items created")
return doc
# ---------------------------------------------------------------------------
-# Seed: Decision template - Processus Runtime Upgrade
+# Seed: Decision template - Runtime Upgrade
# ---------------------------------------------------------------------------
RUNTIME_UPGRADE_STEPS: list[dict] = [
@@ -474,15 +471,16 @@ async def seed_decision_runtime_upgrade(session: AsyncSession) -> Decision:
session,
Decision,
"title",
- "Processus Runtime Upgrade",
+ "Runtime Upgrade",
description=(
- "Template de decision pour les mises a jour du runtime Duniter V2. "
- "5 etapes : qualification, revue, vote, execution, suivi."
+ "Processus de mise a jour du runtime Duniter V2 on-chain. "
+ "Chaque upgrade suit un protocole strict en 5 etapes. "
+ "(Decision on-chain)"
),
decision_type="runtime_upgrade",
status="draft",
)
- print(f" Decision 'Processus Runtime Upgrade': {'created' if created else 'exists'}")
+ print(f" Decision 'Runtime Upgrade': {'created' if created else 'exists'}")
if created:
for step_data in RUNTIME_UPGRADE_STEPS:
@@ -509,16 +507,16 @@ async def run_seed():
print("\n[1/5] Formula Configs...")
formulas = await seed_formula_configs(session)
- print("\n[2/5] Voting Protocols...")
+ print("\n[2/5] Voting Protocols (premier pack de modalites)...")
await seed_voting_protocols(session, formulas)
- print("\n[3/5] Document: Licence G1...")
- await seed_document_licence_g1(session)
+ print("\n[3/5] Document: Acte d'engagement certification...")
+ await seed_document_engagement_certification(session)
- print("\n[4/5] Document: Engagement Forgeron v2.0.0...")
- await seed_document_forgeron(session)
+ print("\n[4/5] Document: Acte d'engagement forgeron...")
+ await seed_document_engagement_forgeron(session)
- print("\n[5/5] Decision: Processus Runtime Upgrade...")
+ print("\n[5/5] Decision: Runtime Upgrade...")
await seed_decision_runtime_upgrade(session)
print("\n" + "=" * 60)
diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml
index a4a3afa..ed90d9b 100644
--- a/docker/docker-compose.dev.yml
+++ b/docker/docker-compose.dev.yml
@@ -1,37 +1,73 @@
-version: "3.9"
-
-# Dev overrides -- usage:
-# docker compose -f docker/docker-compose.yml -f docker/docker-compose.dev.yml up
+# Development stack -- standalone (no Traefik needed)
+# Usage: docker compose -f docker/docker-compose.dev.yml up
+# Ports: frontend 3002, backend 8002, postgres 5432, IPFS API 5001, IPFS GW 8080
services:
postgres:
+ image: postgres:16-alpine
+ restart: unless-stopped
+ environment:
+ POSTGRES_DB: ${POSTGRES_DB:-glibredecision}
+ POSTGRES_USER: ${POSTGRES_USER:-glibredecision}
+ POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-glibredecision-dev}
ports:
- "5432:5432"
+ volumes:
+ - postgres-data:/var/lib/postgresql/data
+ healthcheck:
+ test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-glibredecision}"]
+ interval: 5s
+ timeout: 3s
+ retries: 10
+ start_period: 10s
backend:
build:
+ context: ../
+ dockerfile: docker/backend.Dockerfile
target: development
+ depends_on:
+ postgres:
+ condition: service_healthy
volumes:
- ../backend:/app
ports:
- "8002:8002"
environment:
+ DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER:-glibredecision}:${POSTGRES_PASSWORD:-glibredecision-dev}@postgres:5432/${POSTGRES_DB:-glibredecision}
+ SECRET_KEY: dev-secret-key-not-for-production
DEBUG: "true"
+ ENVIRONMENT: development
CORS_ORIGINS: '["http://localhost:3002"]'
- labels: []
+ DUNITER_RPC_URL: ${DUNITER_RPC_URL:-wss://gdev.p2p.legal/ws}
+ IPFS_API_URL: http://ipfs:5001
+ IPFS_GATEWAY_URL: http://ipfs:8080
frontend:
build:
+ context: ../
+ dockerfile: docker/frontend.Dockerfile
target: development
+ depends_on:
+ - backend
volumes:
- ../frontend:/app
+ - frontend-node-modules:/app/node_modules
ports:
- "3002:3002"
environment:
NUXT_PUBLIC_API_BASE: http://localhost:8002/api/v1
- labels: []
ipfs:
+ image: ipfs/kubo:latest
+ restart: unless-stopped
ports:
- "5001:5001"
- "8080:8080"
+ volumes:
+ - ipfs-data:/data/ipfs
+
+volumes:
+ postgres-data:
+ ipfs-data:
+ frontend-node-modules:
diff --git a/docker/frontend.Dockerfile b/docker/frontend.Dockerfile
index bcc9227..4f42238 100644
--- a/docker/frontend.Dockerfile
+++ b/docker/frontend.Dockerfile
@@ -42,4 +42,10 @@ FROM base AS development
ENV NODE_ENV=development
WORKDIR /app
-ENTRYPOINT ["npm", "run", "dev"]
+
+COPY frontend/package.json ./
+RUN npm install
+
+EXPOSE 3002
+
+CMD ["npm", "run", "dev"]
diff --git a/frontend/app/app.vue b/frontend/app/app.vue
index a7a6e20..f574f7d 100644
--- a/frontend/app/app.vue
+++ b/frontend/app/app.vue
@@ -1,6 +1,7 @@
+
+
+
+ {{ subtitle }}
+
+ {{ description }}
+
+ {{ title }}
+
+
+ {{ title }}
+
+
+
diff --git a/frontend/app/components/decisions/DecisionWorkflow.vue b/frontend/app/components/decisions/DecisionWorkflow.vue
index fc778fc..66ddbc1 100644
--- a/frontend/app/components/decisions/DecisionWorkflow.vue
+++ b/frontend/app/components/decisions/DecisionWorkflow.vue
@@ -96,7 +96,7 @@ function formatDate(dateStr: string): string {
{{ stepTypeLabel(step.step_type) }}
-
Modifications
-Texte propose
diff --git a/frontend/app/components/mandates/MandateTimeline.vue b/frontend/app/components/mandates/MandateTimeline.vue
index 9229b6f..94f78a7 100644
--- a/frontend/app/components/mandates/MandateTimeline.vue
+++ b/frontend/app/components/mandates/MandateTimeline.vue
@@ -98,7 +98,7 @@ function formatDate(dateStr: string): string {
{{ stepTypeLabel(step.step_type) }}
-
- Processus de decision collectifs de la communaute
+ {{ decisions.error }} Aucune decision trouvee
+ Essayez de modifier vos filtres
+ {{ decision.description }} +
+{{ decisions.error }}
-+ Aucun protocole configure +
- - - -Aucune decision pour le moment
-| Titre | -Type | -Statut | -Etapes | -Date | -
|---|---|---|---|---|
|
-
-
-
- {{ decision.title }}
-
- - {{ decision.description }} - - |
-
- |
-
- |
- - {{ decision.steps.length }} - | -- {{ formatDate(decision.created_at) }} - | -
Ancrage IPFS
Ancrage on-chain :
-
- Documents fondateurs de la communaute Duniter/G1 sous vote permanent
+ {{ documents.error }} Aucun document trouve
+ Essayez de modifier vos filtres
{{ documents.error }}
++ {{ doc.description }} +
+ Aucun protocole configure +
+ + - -- Decisions collectives pour la communaute Duniter/G1 +
+ Plateforme de decisions collectives pour la communaute Duniter / G1. + Explorez les documents de reference, participez aux decisions, suivez les mandats.
- {{ stat.label }} -
-- {{ stat.value }} -
-- {{ stat.total }} au total -
-+ {{ card.countLabel }} +
++ {{ card.totalLabel }} +
+ + + + ++ {{ card.description }} +
+ + ++ Connectez-vous avec votre identite Duniter pour participer aux decisions collectives. +
++ Authentification par signature Ed25519 — aucun mot de passe. +
Aucune decision pour le moment
-- {{ decision.title }} -
-- {{ formatDate(decision.updated_at) }} -
-Aucune session de vote pour le moment
-- {{ formatDate(session.created_at) }} -
-- {{ section.description }} -
-- Le seuil d'adoption s'adapte dynamiquement a la participation : - faible participation = quasi-unanimite requise ; forte participation = majorite simple suffisante. -
-
- Seuil = C + B^W + (M + (1-M) * (1 - (T/W)^G)) * max(0, T-C)
-
- + Le seuil d'adoption s'adapte dynamiquement a la participation : + faible participation = quasi-unanimite requise ; forte participation = majorite simple suffisante. +
+
+ Seuil = C + B^W + (M + (1-M) * (1 - (T/W)^G)) * max(0, T-C)
+
+ - Mandats de gouvernance : comite technique, forgerons et roles specifiques -
-{{ mandates.error }}
{{ mandates.error }}
+ +Aucun mandat pour le moment
++ Un mandat definit un contexte, un objectif et une duree pour une mission de gouvernance. + Il peut porter sur le comite technique, les forgerons, ou tout role specifique de la communaute. +
++ Par defaut, un mandat nomme un binome pour assurer la continuite. + Le processus comprend : candidature, vote communautaire, periode active et rapport final. +
+Aucun mandat trouve
++ Essayez de modifier vos filtres +
++ {{ mandate.description }} +
++ Aucun protocole configure +
+ + + + +- Configuration des protocoles de vote et formules de seuil WoT +
+ Modalites de vote et formules configurables
{{ protocols.error }}
-{{ protocols.error }}
+Aucun protocole de vote configure
-Aucun protocole de vote configure
- {{ protocol.description }} -
-+ {{ protocol.description }} +
- -Aucune configuration de formule
-Aucune configuration de formule
| Nom | -Duree | -Majorite | -B | -G | -C | -Smith | -TechComm | -Date | -
|---|---|---|---|---|---|---|---|---|
| - {{ formula.name }} - | -{{ formula.duration_days }}j | -{{ formula.majority_pct }}% | -{{ formula.base_exponent }} | -{{ formula.gradient_exponent }} | -{{ formula.constant_base }} | -- {{ formula.smith_exponent ?? '-' }} - | -- {{ formula.techcomm_exponent ?? '-' }} - | -- {{ formatDate(formula.created_at) }} - | -
| Nom | +Duree | +Majorite | +B | +G | +C | +Smith | +TechComm | +Date | +
|---|---|---|---|---|---|---|---|---|
| {{ formula.name }} | +{{ formula.duration_days }}j | +{{ formula.majority_pct }}% | +{{ formula.base_exponent }} | +{{ formula.gradient_exponent }} | +{{ formula.constant_base }} | +{{ formula.smith_exponent ?? '-' }} | +{{ formula.techcomm_exponent ?? '-' }} | +{{ formatDate(formula.created_at) }} | +
- Seuil = C + B^W + (M + (1-M) * (1 - (T/W)^G)) * max(0, T-C)
-
-
+ Seuil = C + B^W + (M + (1-M) * (1 - (T/W)^G)) * max(0, T-C)
+
+ + L'inertie fonctionne ainsi : faible participation implique quasi-unanimite requise ; + forte participation permet une majorite simple suffisante. + Cela protege contre les votes precipites tout en permettant l'efficacite + lorsque la communaute est mobilisee. +
+