Refonte design : 4 humeurs, onboarding, sections avec boite a outils

- Systeme de themes adaptatifs : Peps (light chaud), Zen (light calme),
  Chagrine (dark violet), Grave (dark ambre) avec CSS custom properties
- Dashboard d'accueil orienté onboarding avec cartes-portes et teaser
  boite a outils
- SectionLayout reutilisable : liste + sidebar toolbox + status pills
  cliquables (En prepa / En vote / En vigueur / Clos)
- ToolboxVignette : vignettes Contexte / Tutos / Choisir / Demarrer
- Seed : Acte engagement certification + forgeron, Runtime Upgrade
  (decision on-chain), 3 modalites de vote (majoritaire, quadratique,
  permanent)
- Backend adapte SQLite (Uuid portable, 204 fix, pool conditionnel)
- Correction noms composants (pathPrefix: false), pinia/nuxt ^0.11

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Yvv
2026-02-28 17:44:48 +01:00
parent 403b94fa2c
commit 77dceb49c3
49 changed files with 20628 additions and 1180 deletions

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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))

View File

@@ -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())

View File

@@ -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

View File

@@ -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.

View File

@@ -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),

View File

@@ -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,

View File

@@ -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),

View File

@@ -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

View File

@@ -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)