Files
decision/backend/seed.py
Yvv 3de07e8c17 Fix: accents manquants dans seed + labels type visibles
- Reseed avec tous les accents français corrigés (à, é, è, ê, î, ô)
- Labels type-étiquette: taille augmentée, fond accent léger, visible

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 09:45:15 +01:00

1509 lines
55 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Seed the database with real data from Duniter community documents.
Sources:
- Engagement Forgeron v2.0.0: https://forum.monnaie-libre.fr/t/33165
- Engagement Certification (Licence G1 v0.3.0):
https://git.duniter.org/documents/g1_monetary_license/-/raw/master/g1_monetary_license_fr.rst
- Charte 1.0 (rejected): https://forum.monnaie-libre.fr/t/proposition-charte-1-0/31066
- Licence v0.4.0 (in progress): https://forum.monnaie-libre.fr/t/32375
- Runtime Upgrade process template
Genesis references:
- Licence repo: https://git.duniter.org/documents/g1_monetary_license
- g1vote: https://git.duniter.org/tools/g1vote-view
- g1vote live: https://g1vote-view-237903.pages.duniter.org/
Idempotent: checks if data already exists before inserting.
"""
from __future__ import annotations
import asyncio
import hashlib
import json
import uuid
from datetime import datetime, timedelta, timezone
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.database import async_session, engine, Base, init_db
from app.models.protocol import FormulaConfig, VotingProtocol
from app.models.document import Document, DocumentItem
from app.models.decision import Decision, DecisionStep
from app.models.user import DuniterIdentity
from app.models.vote import VoteSession, Vote
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
async def get_or_create(
session: AsyncSession,
model,
lookup_field: str,
lookup_value,
**kwargs,
):
"""Return existing row or create a new one."""
stmt = select(model).where(getattr(model, lookup_field) == lookup_value)
result = await session.execute(stmt)
instance = result.scalar_one_or_none()
if instance is not None:
return instance, False
instance = model(**{lookup_field: lookup_value}, **kwargs)
session.add(instance)
await session.flush()
return instance, True
def fake_signature(payload: str) -> str:
"""Generate a deterministic fake Ed25519-like signature for seed data."""
return hashlib.sha256(payload.encode()).hexdigest()
# ---------------------------------------------------------------------------
# Seed: FormulaConfigs
# ---------------------------------------------------------------------------
async def seed_formula_configs(session: AsyncSession) -> dict[str, FormulaConfig]:
configs: dict[str, dict] = {
"Standard Licence G1": {
"description": "Formule standard pour la Licence G1 : vote binaire WoT. Inertie standard.",
"duration_days": 30,
"majority_pct": 50,
"base_exponent": 0.1,
"gradient_exponent": 0.2,
"constant_base": 0.0,
},
"Inertie basse (Annexes)": {
"description": (
"Formule à inertie basse pour les annexes et recommandations. "
"Gradient faible = plus facile à remplacer même à faible participation."
),
"duration_days": 30,
"majority_pct": 50,
"base_exponent": 0.1,
"gradient_exponent": 0.1,
"constant_base": 0.0,
},
"Inertie haute (Formule)": {
"description": (
"Formule à inertie haute pour la section formule elle-même. "
"Gradient élevé = nécessite une forte mobilisation pour changer."
),
"duration_days": 30,
"majority_pct": 60,
"base_exponent": 0.1,
"gradient_exponent": 0.4,
"constant_base": 0.0,
},
"Inertie très haute (Méta-réglage)": {
"description": (
"Formule à inertie maximale pour le réglage de l'inertie elle-même. "
"Quasi-unanimité requise à toute participation. Protection contre "
"la modification des règles de modification."
),
"duration_days": 30,
"majority_pct": 66,
"base_exponent": 0.1,
"gradient_exponent": 0.6,
"constant_base": 0.0,
},
"Forgeron avec Smith": {
"description": "Vote forgeron avec critère Smith sub-WoT.",
"duration_days": 30,
"majority_pct": 50,
"base_exponent": 0.1,
"gradient_exponent": 0.2,
"constant_base": 0.0,
"smith_exponent": 0.1,
},
"Comité Tech": {
"description": "Vote avec critère Comité Technique.",
"duration_days": 30,
"majority_pct": 50,
"base_exponent": 0.1,
"gradient_exponent": 0.2,
"constant_base": 0.0,
"techcomm_exponent": 0.1,
},
"Vote Nuance": {
"description": "Vote nuancé à 6 niveaux (CONTRE..TOUT À FAIT).",
"duration_days": 30,
"majority_pct": 50,
"base_exponent": 0.1,
"gradient_exponent": 0.2,
"constant_base": 0.0,
"nuanced_min_participants": 59,
"nuanced_threshold_pct": 80,
},
}
result: dict[str, FormulaConfig] = {}
for name, params in configs.items():
instance, created = await get_or_create(
session, FormulaConfig, "name", name, **params,
)
status = "created" if created else "exists"
print(f" FormulaConfig '{name}': {status}")
result[name] = instance
return result
# ---------------------------------------------------------------------------
# Seed: VotingProtocols
# ---------------------------------------------------------------------------
async def seed_voting_protocols(
session: AsyncSession,
formulas: dict[str, FormulaConfig],
) -> dict[str, VotingProtocol]:
protocols: dict[str, dict] = {
"Vote WoT standard": {
"description": (
"Vote binaire (pour/contre) ouvert à tous les membres de la "
"toile de confiance. Le seuil d'adoption s'adapte au taux de "
"participation via l'inertie WoT : quasi-unanimité à faible "
"participation, majorité simple à forte mobilisation. "
"Utilisé pour la Licence G1 et les textes fondateurs."
),
"vote_type": "binary",
"formula_config_id": formulas["Standard Licence G1"].id,
"mode_params": "D30M50B.1G.2",
},
"Vote forgeron (Smith)": {
"description": (
"Vote binaire avec double critère : seuil WoT standard + "
"seuil minimal de forgerons. Garantit que toute décision "
"impliquant les validateurs soit soutenue par les "
"opérateurs du réseau. Utilisé pour les engagements "
"forgerons et les décisions d'infrastructure."
),
"vote_type": "binary",
"formula_config_id": formulas["Forgeron avec Smith"].id,
"mode_params": "D30M50B.1G.2S.1",
},
"Vote Comité Technique": {
"description": (
"Vote binaire avec critère TechComm obligatoire. "
"Réservé aux décisions techniques critiques : runtime "
"upgrades, modifications de paramètres on-chain, "
"approbation de code. Le Comité Technique doit atteindre "
"un seuil minimal indépendamment du vote communautaire."
),
"vote_type": "binary",
"formula_config_id": formulas["Comité Tech"].id,
"mode_params": "D30M50B.1G.2T.1",
},
}
result: dict[str, VotingProtocol] = {}
for name, params in protocols.items():
instance, created = await get_or_create(
session, VotingProtocol, "name", name, **params,
)
status = "created" if created else "exists"
print(f" VotingProtocol '{name}': {status}")
result[name] = instance
return result
# ---------------------------------------------------------------------------
# Seed: Engagement Certification (Acte d'engagement certification)
#
# Full structured document built from:
# - Licence G1 v0.3.0 (in force)
# - Charte 1.0 (rejected proposal, topic 31066)
# - Licence v0.4.0 (in progress, topic 32375)
# - Yvv's "Acte d'engagement" position
# - Checklist certification (topic 32412)
# ---------------------------------------------------------------------------
GENESIS_CERTIFICATION = {
"source_document": {
"title": "Licence de la monnaie libre G1 v0.3.0",
"url": "https://git.duniter.org/documents/g1_monetary_license/-/raw/master/g1_monetary_license_fr.rst",
"repo": "https://git.duniter.org/documents/g1_monetary_license",
},
"référence_tools": {
"g1vote_repo": "https://git.duniter.org/tools/g1vote-view",
"g1vote_live": "https://g1vote-view-237903.pages.duniter.org/",
"cesium": "https://g1.duniter.org",
"gecko": "https://gecko.music-all.org",
},
"forum_synthesis": [
{
"title": "Proposition Charte 1.0 (rejetée)",
"url": "https://forum.monnaie-libre.fr/t/proposition-charte-1-0/31066",
"status": "rejected",
"posts": 70,
},
{
"title": "Préparation licence v0.4.0",
"url": "https://forum.monnaie-libre.fr/t/preparation-dune-proposition-devolution-de-la-licence-1/32375",
"status": "in_progress",
"posts": 38,
},
{
"title": "Checklist de certification (annexe licence)",
"url": "https://forum.monnaie-libre.fr/t/prepa-checklist-de-certification-annexe-licence-1/32412",
"status": "in_progress",
"posts": 16,
},
{
"title": "Règles de modifications (annexe licence)",
"url": "https://forum.monnaie-libre.fr/t/prepa-regles-de-modifications-annexe-licence-1/32409",
"status": "in_progress",
"posts": 9,
},
{
"title": "Vote nuancé licence",
"url": "https://forum.monnaie-libre.fr/t/processus-de-validation-licence-par-vote-nuance/31729",
"status": "référence",
},
],
"formula_trigger": (
"Quand un item atteint le seuil d'adoption (formule WoT), "
"le texte de remplacement est intégré au document officiel. "
"Le hash IPFS du document mis à jour est ancré on-chain via system.remark. "
"Les applications (Cesium, Gecko) pointent vers le dépôt git officiel "
"qui est synchronisé avec l'état validé par les votes."
),
"contributors": [
{"name": "1000i100", "role": "Pilote principal, rédacteur"},
{"name": "Natha", "role": "Co-rédactrice"},
{"name": "Pini", "role": "Co-initiateur"},
{"name": "Yvv", "role": "Contributions structurelles, concepteur 'Acte d'engagement'"},
{"name": "elois", "role": "Dev g1vote, contributions techniques"},
],
}
ENGAGEMENT_CERTIFICATION_ITEMS: list[dict] = [
# ===================================================================
# INTRODUCTION
# ===================================================================
{
"position": "I1",
"item_type": "preamble",
"title": "Préambule",
"sort_order": 1,
"section_tag": "introduction",
"inertia_preset": "standard",
"current_text": (
"Le présent acte d'engagement définit les obligations réciproques "
"des membres de la toile de confiance de la monnaie libre G1. "
"Cet acte est de fait l'unique relation contractuelle de notre "
"toile fiduciaire. Toute certification doit s'accompagner de la "
"transmission de ce document, dont le certificateur doit s'assurer "
"qu'il a été étudié, compris et accepté par le certifié."
),
},
{
"position": "I2",
"item_type": "preamble",
"title": "Les deux garanties réciproques",
"sort_order": 2,
"section_tag": "introduction",
"inertia_preset": "standard",
"current_text": (
"La certification repose sur deux garanties réciproques :\n\n"
"**1.** Derrière une clé publique créatrice de monnaie "
"se trouve un **être humain vivant**.\n\n"
"**2.** Derrière cet être humain se trouve **une seule et unique** "
"clé publique créatrice de monnaie.\n\n"
"La certification est un acte technique et fiduciaire, "
"pas un acte d'adhésion morale ou de sympathie."
),
},
# ===================================================================
# TITRE 1 : ENGAGEMENTS FONDAMENTAUX (sur l'honneur)
# ===================================================================
{
"position": "T1",
"item_type": "section",
"title": "Engagements fondamentaux",
"sort_order": 3,
"section_tag": "fondamental",
"inertia_preset": "standard",
"current_text": (
"Les engagements fondamentaux ci-dessous constituent le socle "
"irréductible de l'acte d'engagement. Ils sont pris sur l'honneur "
"par tout membre de la toile de confiance."
),
},
{
"position": "E1",
"item_type": "clause",
"title": "Unicité du compte",
"sort_order": 4,
"section_tag": "fondamental",
"inertia_preset": "standard",
"current_text": (
"Je m'engage sur l'honneur à n'avoir et n'avoir jamais "
"qu'un seul et unique compte cocréateur de monnaie G1."
),
},
{
"position": "E2",
"item_type": "clause",
"title": "Réciprocité",
"sort_order": 5,
"section_tag": "fondamental",
"inertia_preset": "standard",
"current_text": (
"Je m'engage sur l'honneur à ne certifier que des personnes "
"physiques qui respectent scrupuleusement ces deux présents "
"engagements fondamentaux."
),
},
# ===================================================================
# TITRE 2 : ENGAGEMENTS TECHNIQUES
# ===================================================================
{
"position": "T2",
"item_type": "section",
"title": "Engagements techniques",
"sort_order": 6,
"section_tag": "technique",
"inertia_preset": "standard",
"current_text": (
"Les engagements techniques définissent les obligations "
"pratiques et vérifiables du certificateur pour garantir "
"la qualité de la toile de confiance."
),
},
{
"position": "E3",
"item_type": "clause",
"title": "Connaissance suffisante",
"sort_order": 7,
"section_tag": "technique",
"inertia_preset": "standard",
"current_text": (
"Je me suis assuré de connaître suffisamment la personne "
"qui gère cette clé publique. Connaître suffisamment ne signifie "
"pas « avoir vu » ; c'est assurer à la communauté G1 que je "
"pourrai la contacter facilement et être en mesure de repérer "
"un double-compte ou tout autre problème."
),
},
{
"position": "E4",
"item_type": "clause",
"title": "Vérification personnelle de la clé",
"sort_order": 8,
"section_tag": "technique",
"inertia_preset": "standard",
"current_text": (
"J'ai personnellement vérifié que c'est bien cette clé publique "
"que je m'apprête à certifier, en la comparant avec la personne "
"concernée et non par un intermédiaire."
),
},
{
"position": "E5",
"item_type": "clause",
"title": "Joignabilité réciproque",
"sort_order": 9,
"section_tag": "technique",
"inertia_preset": "standard",
"current_text": (
"Je suis joignable rapidement et facilement par mes certifieurs, "
"et je peux joindre rapidement et facilement les personnes que "
"je certifie, par plusieurs moyens de communication différents "
"et indépendants (physique, téléphone, email, messagerie, etc.)."
),
},
{
"position": "E6",
"item_type": "clause",
"title": "Document de révocation",
"sort_order": 10,
"section_tag": "technique",
"inertia_preset": "standard",
"current_text": (
"J'ai vérifié avec la personne certifiée qu'elle a bien généré "
"son document de révocation de compte et qu'elle le conserve "
"en lieu sûr."
),
},
{
"position": "E7",
"item_type": "clause",
"title": "Rencontre physique ou vérification multi-canaux",
"sort_order": 11,
"section_tag": "technique",
"inertia_preset": "standard",
"current_text": (
"J'ai rencontré la personne physiquement (préférable), **OU** "
"j'ai vérifié à distance le lien personne / clé publique par "
"plusieurs moyens de communication différents et indépendants : "
"courrier + réseau social + forum + email + visio + téléphone."
),
},
# ===================================================================
# TITRE 3 : CONSEILS ET BONNES PRATIQUES
# ===================================================================
{
"position": "T3",
"item_type": "section",
"title": "Conseils et bonnes pratiques",
"sort_order": 12,
"section_tag": "technique",
"inertia_preset": "standard",
"current_text": (
"Les pratiques suivantes sont fortement recommandées pour "
"garantir la qualité et la sécurité de la toile de confiance."
),
},
{
"position": "E8",
"item_type": "clause",
"title": "Ne jamais certifier seul",
"sort_order": 13,
"section_tag": "technique",
"inertia_preset": "standard",
"current_text": (
"Ne certifiez jamais seul, mais accompagné d'au moins un autre "
"membre de la toile de confiance G1, pour garantir un double "
"contrôle."
),
},
{
"position": "E9",
"item_type": "clause",
"title": "Vérification des certifications existantes",
"sort_order": 14,
"section_tag": "technique",
"inertia_preset": "standard",
"current_text": (
"Avant toute certification, vérifiez si le compte a déjà "
"reçu des certifications et de qui elles proviennent. "
"Contactez les certifieurs existants en cas de doute. "
"Si un certifieurs existant ne connaît pas la personne, "
"alertez immédiatement les experts de la communauté."
),
},
{
"position": "E10",
"item_type": "clause",
"title": "Vérification de maîtrise du compte",
"sort_order": 15,
"section_tag": "technique",
"inertia_preset": "standard",
"current_text": (
"Vérifiez la maîtrise du compte par un transfert-test : "
"envoyez quelques G1 et demandez le renvoi, afin de vous "
"assurer que la personne contrôle bien sa clé privée."
),
},
{
"position": "E11",
"item_type": "clause",
"title": "Transmission et compréhension du document",
"sort_order": 16,
"section_tag": "technique",
"inertia_preset": "standard",
"current_text": (
"Vérifiez que vos contacts ont bien étudié et compris "
"le présent acte d'engagement dans sa version à jour "
"avant de procéder à la certification."
),
},
# ===================================================================
# CONCLUSION
# ===================================================================
{
"position": "K1",
"item_type": "preamble",
"title": "Règles abrégées de la toile de confiance",
"sort_order": 17,
"section_tag": "conclusion",
"inertia_preset": "standard",
"current_text": (
"**Paramètres protocolaires en vigueur :**\n\n"
"- Stock de **100 certifications** possibles\n"
"- 1 certification émissible tous les **5 jours**\n"
"- Nouveau compte valide si **>= 5 certifications** reçues en **2 mois**\n"
"- Condition de distance : **<= 5 pas** de **80% des sentinelles**\n"
"- Sentinelle : membre ayant reçu et émis >= Y[N] certifs "
"(Y = ceil(N^(1/5)))\n"
"- Certifications actives valables **2 ans**\n"
"- Renouvellement de l'accord tous les **12 mois**\n\n"
"*Note : le vote porte sur l'inclusion de ces règles dans le document, "
"pas sur les valeurs des variables protocolaires elles-mêmes, "
"qui sont fixées par le protocole Duniter.*"
),
},
{
"position": "K2",
"item_type": "preamble",
"title": "Monnaie G1",
"sort_order": 18,
"section_tag": "conclusion",
"inertia_preset": "standard",
"current_text": (
"**Paramètres monétaires :**\n\n"
"- 1 Dividende Universel (DU) par personne par jour\n"
"- Réévaluation à chaque équinoxe : "
"`DU(n+1) = DU(n) + c² × (M/N) / 182.625` avec c = 4.88%\n"
"- DU(0) = 10.00 G1\n\n"
"*Note : le vote porte sur l'inclusion de ces paramètres dans le document, "
"pas sur les valeurs monétaires elles-mêmes, "
"qui découlent de la TRM et du bloc 0.*"
),
},
# ===================================================================
# ANNEXE 1 : INTEGRATION LOGICIELLE
# ===================================================================
{
"position": "X1",
"item_type": "section",
"title": "Annexe 1 : Intégration logicielle",
"sort_order": 19,
"section_tag": "annexe",
"inertia_preset": "low",
"current_text": (
"Les obligations pour les logiciels implémentant la certification G1."
),
},
{
"position": "X1.1",
"item_type": "clause",
"title": "Dépôt de référence",
"sort_order": 20,
"section_tag": "annexe",
"inertia_preset": "low",
"current_text": (
"Le texte de référence de l'acte d'engagement certification est "
"hébergé dans le dépôt git officiel : "
"https://git.duniter.org/documents/g1_monetary_license\n\n"
"Les applications Cesium, Gecko et toute application de "
"certification doivent pointer vers ce dépôt pour afficher "
"la version en vigueur."
),
},
{
"position": "X1.2",
"item_type": "clause",
"title": "Obligation de transmission",
"sort_order": 21,
"section_tag": "annexe",
"inertia_preset": "low",
"current_text": (
"Tout logiciel G1 permettant la certification doit transmettre "
"le présent acte d'engagement au certifié et en afficher les "
"paramètres du bloc 0 de la blockchain Duniter."
),
},
{
"position": "X1.3",
"item_type": "clause",
"title": "Logiciel auditable",
"sort_order": 22,
"section_tag": "annexe",
"inertia_preset": "low",
"current_text": (
"Tout logiciel utilisé pour la certification ou la création "
"monétaire doit être publié sous licence libre, afin de "
"permettre son audit par la communauté."
),
},
# ===================================================================
# ANNEXE 2 : QUESTIONS A LA CERTIFICATION (checklist logicielle)
# ===================================================================
{
"position": "X2",
"item_type": "section",
"title": "Annexe 2 : Questions à la certification",
"sort_order": 23,
"section_tag": "annexe",
"inertia_preset": "low",
"current_text": (
"Liste de questions à présenter dans les logiciels de certification. "
"Inspirée de la checklist de la Charte 1.0 et des discussions "
"forum (topic 32412)."
),
},
{
"position": "X2.1",
"item_type": "verification",
"title": "Questions piège (réponse attendue : NON)",
"sort_order": 24,
"section_tag": "annexe",
"inertia_preset": "low",
"current_text": (
"**Si OUI à l'une de ces questions, la certification doit être refusée.**\n\n"
"- La personne m'a contacté uniquement pour obtenir ma certification\n"
"- Je certifie sous la pression ou pour faire plaisir\n"
"- Je n'ai aucun moyen de vérifier l'identité de la personne\n"
"- La personne refuse de me fournir plusieurs moyens de contact\n"
"- Je soupçonne que la personne possède déjà un compte membre\n"
"- Je ne connais aucun de ses autres certifieurs\n"
"- La personne ne comprend pas ce qu'est la monnaie libre\n"
"- La personne n'a pas lu le présent acte d'engagement"
),
},
{
"position": "X2.2",
"item_type": "verification",
"title": "Questions identité (réponse attendue : OUI)",
"sort_order": 25,
"section_tag": "annexe",
"inertia_preset": "low",
"current_text": (
"**Si NON à l'une de ces questions, la certification doit être refusée.**\n\n"
"- Je connais cette personne suffisamment pour la recontacter\n"
"- J'ai vérifié personnellement le lien personne / clé publique\n"
"- La personne est une personne physique vivante (pas une entité morale)\n"
"- Je pourrais reconnaître cette personne si je la croisais\n"
"- J'ai au moins 2 moyens de contact différents pour cette personne"
),
},
{
"position": "X2.3",
"item_type": "verification",
"title": "Questions sécurité (réponse attendue : OUI)",
"sort_order": 26,
"section_tag": "annexe",
"inertia_preset": "low",
"current_text": (
"**Si NON, la certification est déconseillée.**\n\n"
"- La personne a généré son document de révocation\n"
"- La personne maîtrise effectivement son compte "
"(test de transfert effectué)\n"
"- La personne sait où retrouver le présent acte d'engagement "
"dans sa version à jour"
),
},
# ===================================================================
# SECTION SPECIALE : FORMULE DE VOTE
# ===================================================================
{
"position": "F1",
"item_type": "section",
"title": "Formule de vote",
"sort_order": 27,
"section_tag": "formule",
"inertia_preset": "high",
"current_text": (
"La formule qui régit l'adoption ou le rejet de chaque modification "
"du présent document. Référence : g1vote "
"(https://g1vote-view-237903.pages.duniter.org/)."
),
},
{
"position": "F1.1",
"item_type": "rule",
"title": "Formule du seuil WoT",
"sort_order": 28,
"section_tag": "formule",
"inertia_preset": "high",
"current_text": (
"**Seuil = C + B^W + (M + (1-M) × (1 - (T/W)^G)) × max(0, T-C)**\n\n"
"Où :\n"
"- **C** = constante de base (plancher fixe)\n"
"- **B** = exposant de base (B^W tend vers 0)\n"
"- **W** = taille de la toile de confiance (électeurs éligibles)\n"
"- **T** = total des votes exprimés (pour + contre)\n"
"- **M** = ratio de majorité cible (M = majority_pct / 100)\n"
"- **G** = exposant du gradient d'inertie\n\n"
"**Comportement :** Faible participation → quasi-unanimité requise. "
"Forte participation → majorité simple M suffit."
),
},
{
"position": "F1.2",
"item_type": "rule",
"title": "Paramètres par défaut",
"sort_order": 29,
"section_tag": "formule",
"inertia_preset": "high",
"current_text": (
"**Seuil = M + (1-M) × (1 - (T/W)^G)**\n\n"
"- **T** = votes exprimés (pour + contre)\n"
"- **W** = taille de la toile de confiance\n"
"- **M** = majorité cible (50% par défaut)\n"
"- **G** = gradient d'inertie (0.2 par défaut)\n\n"
"**Valeurs par défaut (inertie standard) :**\n"
"- Durée : 30 jours (vote permanent)\n"
"- Majorité cible M = 50%\n"
"- Gradient G = 0.2\n"
"- Base B = 0.1, Constante C = 0\n\n"
"**Lecture du curseur d'inertie :**\n"
"Le curseur affiché sur chaque section représente le niveau G. "
"Plus le curseur est à droite, plus le remplacement est difficile. "
"La courbe associée montre le seuil requis en fonction de la participation.\n\n"
"Mode compact : **D30M50B.1G.2**"
),
},
{
"position": "F1.3",
"item_type": "rule",
"title": "Processus de dépôt officiel",
"sort_order": 30,
"section_tag": "formule",
"inertia_preset": "high",
"current_text": (
"Lorsqu'une proposition alternative atteint le seuil d'adoption :\n\n"
"1. Le texte de remplacement est intégré au document officiel\n"
"2. Le hash IPFS du document mis à jour est calculé\n"
"3. Le hash est ancré on-chain via `system.remark`\n"
"4. Le dépôt git officiel est synchronisé\n"
"5. Les applications (Cesium, Gecko) mettent à jour automatiquement"
),
},
# ===================================================================
# SECTION SPECIALE : REGLAGE DE L'INERTIE
# ===================================================================
{
"position": "N1",
"item_type": "section",
"title": "Réglage de l'inertie",
"sort_order": 31,
"section_tag": "inertie",
"inertia_preset": "very_high",
"current_text": (
"Le réglage de l'inertie définit la difficulté de remplacement "
"de chaque section du document. Ce réglage est lui-même soumis "
"à l'inertie la plus élevée, pour empêcher la modification "
"des règles de modification."
),
},
{
"position": "N1.1",
"item_type": "rule",
"title": "Niveaux d'inertie",
"sort_order": 32,
"section_tag": "inertie",
"inertia_preset": "very_high",
"current_text": (
"**Formule simplifiée :** `Seuil = M + (1-M) × (1 - (T/W)^G)`\n\n"
"Le **gradient G** contrôle la courbe d'inertie : plus G est élevé, "
"plus le seuil reste haut même avec une participation croissante.\n\n"
"**Basse** — G=0.1, M=50%\n"
"- La courbe descend très vite vers 50%\n"
"- Dès 10% de participation, le seuil est proche de la majorité simple\n"
"- *Usage : annexes, recommandations, conseils*\n\n"
"**Standard** — G=0.2, M=50%\n"
"- Descente progressive, équilibre entre stabilité et réactivité\n"
"- À 25% de participation, le seuil avoisine 65%\n"
"- *Usage : engagements fondamentaux et techniques*\n\n"
"**Haute** — G=0.4, M=60%\n"
"- Courbe résistante, le seuil reste élevé longtemps\n"
"- Super-majorité de 60% même à pleine participation\n"
"- *Usage : formule de vote elle-même*\n\n"
"**Très haute** — G=0.6, M=66%\n"
"- Quasi-unanimité requise à tout niveau de participation\n"
"- Seul un consensus massif (66%+) peut modifier ce réglage\n"
"- *Usage : réglage de l'inertie (méta-protection)*\n\n"
"Le curseur d'inertie associé à chaque section du document "
"reflète visuellement ces niveaux."
),
},
# ===================================================================
# SECTION SPECIALE : ORDONNANCEMENT
# ===================================================================
{
"position": "O1",
"item_type": "section",
"title": "Ordonnancement du document",
"sort_order": 33,
"section_tag": "ordonnancement",
"inertia_preset": "standard",
"current_text": (
"L'ordre de présentation des items dans le document est "
"lui-même soumis au vote. Toute proposition de réorganisation "
"doit atteindre le seuil d'adoption avec l'inertie standard."
),
},
]
async def seed_document_engagement_certification(
session: AsyncSession,
protocols: dict[str, VotingProtocol],
) -> Document:
genesis = json.dumps(GENESIS_CERTIFICATION, ensure_ascii=False, indent=2)
doc, created = await get_or_create(
session,
Document,
"slug",
"engagement-certification",
title="Acte d'engagement certification",
doc_type="engagement",
version="1.0.0",
status="active",
description=(
"Acte d'engagement des certificateurs de la toile de confiance G1. "
"Document modulaire sous vote permanent : chaque item peut être "
"remplacé par une alternative qui atteint le seuil d'adoption. "
"Construit à partir de la Licence G1 v0.3.0, des discussions "
"communautaires et de la position 'Acte d'engagement' (Yvv)."
),
genesis_json=genesis,
)
print(f" Document 'Acte d'engagement certification': {'created' if created else 'exists'}")
if created:
# Map inertia presets to voting protocols
inertia_protocol_map = {
"low": protocols.get("Vote WoT standard"),
"standard": protocols.get("Vote WoT standard"),
"high": protocols.get("Vote WoT standard"),
"very_high": protocols.get("Vote WoT standard"),
}
for item_data in ENGAGEMENT_CERTIFICATION_ITEMS:
preset = item_data.get("inertia_preset", "standard")
protocol = inertia_protocol_map.get(preset)
item = DocumentItem(
document_id=doc.id,
voting_protocol_id=protocol.id if protocol else None,
**item_data,
)
session.add(item)
await session.flush()
print(f" -> {len(ENGAGEMENT_CERTIFICATION_ITEMS)} items created")
return doc
# ---------------------------------------------------------------------------
# Seed: Engagement Forgeron v2.0.0 (real content from forum topic 33165)
# ---------------------------------------------------------------------------
ENGAGEMENT_FORGERON_ITEMS: list[dict] = [
# --- Aspirant Forgeron : Sécurité et conformité ---
{
"position": "A1",
"item_type": "clause",
"title": "Intention et motivation",
"sort_order": 1,
"current_text": (
"J'ai clarifié ce qui me motive à devenir forgeron, "
"j'en assume les raisons."
),
},
{
"position": "A2",
"item_type": "clause",
"title": "Veille sécurité",
"sort_order": 2,
"current_text": (
"Je fais de la veille pour maintenir mes pratiques de sécurité "
"système et réseau à jour."
),
},
{
"position": "A3",
"item_type": "clause",
"title": "Notifications forum",
"sort_order": 3,
"current_text": (
"J'ai activé les notifications sur forum.duniter.org pour être "
"alerté des discussions importantes concernant le réseau."
),
},
{
"position": "A4",
"item_type": "verification",
"title": "Phrase de récupération aléatoire",
"sort_order": 4,
"current_text": (
"Je confirme que ma phrase de récupération a été générée "
"aléatoirement et n'est pas une phrase choisie par moi."
),
},
{
"position": "A5",
"item_type": "verification",
"title": "Compte séparé",
"sort_order": 5,
"current_text": (
"J'utilise un autre compte pour mes transactions courantes ; "
"le compte forgeron est strictement réservé à la validation."
),
},
{
"position": "A6",
"item_type": "verification",
"title": "Sauvegarde phrase de récupération",
"sort_order": 6,
"current_text": (
"J'ai stocké ma phrase de récupération sur plusieurs supports "
"physiques distincts et sécurisés."
),
},
{
"position": "A7",
"item_type": "verification",
"title": "Noeud à jour et synchronisé",
"sort_order": 7,
"current_text": (
"Je gère déjà un noeud à jour, correctement synchronisé et "
"joignable par les autres noeuds du réseau."
),
},
{
"position": "A8",
"item_type": "verification",
"title": "API unsafe non exposée",
"sort_order": 8,
"current_text": (
"J'ai veillé à ne pas exposer publiquement l'api unsafe "
"de mon noeud validateur."
),
},
{
"position": "A9",
"item_type": "clause",
"title": "Transparence technique",
"sort_order": 9,
"current_text": (
"Je fournis à la demande d'un autre forgeron, mes choix "
"techniques (matériel, OS, configuration réseau)."
),
},
{
"position": "A10",
"item_type": "clause",
"title": "Déclaration offline en cas de doute",
"sort_order": 10,
"current_text": (
"Je me déclare offline en cas de doute sur la sécurité "
"de mon noeud ou de mon infrastructure."
),
},
{
"position": "A11",
"item_type": "clause",
"title": "Réactivité 24h",
"sort_order": 11,
"current_text": (
"Je m'engage à répondre en moins de 24h aux forgerons "
"quand je suis déclaré online."
),
},
# --- Aspirant Forgeron : Contact ---
{
"position": "A12",
"item_type": "clause",
"title": "Contact multi-canal",
"sort_order": 12,
"current_text": (
"Je sais joindre efficacement et rapidement 3 des forgerons "
"par au moins 2 canaux de communication différents."
),
},
# --- Aspirant Forgeron : Connaissances ---
{
"position": "A13",
"item_type": "clause",
"title": "Acceptation des engagements",
"sort_order": 13,
"current_text": (
"J'ai lu et j'accepte de respecter l'ensemble des "
"engagements forgerons en vigueur."
),
},
{
"position": "A14",
"item_type": "clause",
"title": "Règles de la TdC forgeron",
"sort_order": 14,
"current_text": (
"J'ai pris connaissance des règles et délais associés au "
"fonctionnement de la TdC forgeron."
),
},
{
"position": "A15",
"item_type": "clause",
"title": "Fonctionnement blockchain",
"sort_order": 15,
"current_text": (
"J'ai bien compris le fonctionnement d'un réseau blockchain "
"Duniter et le rôle du validateur."
),
},
# --- Aspirant Forgeron : Pièges (expected: NON) ---
{
"position": "A16",
"item_type": "rule",
"title": "Piège : harcèlement",
"sort_order": 16,
"current_text": (
"[Piège - réponse attendue : NON] "
"J'insiste, harcèle ou fais pression pour être certifié forgeron."
),
},
{
"position": "A17",
"item_type": "rule",
"title": "Piège : gloire et pouvoir",
"sort_order": 17,
"current_text": (
"[Piège - réponse attendue : NON] "
"Je veux être forgeron pour la gloire et le pouvoir."
),
},
{
"position": "A18",
"item_type": "rule",
"title": "Piège : nuisance",
"sort_order": 18,
"current_text": (
"[Piège - réponse attendue : NON] "
"Je cherche à nuire à l'écosystème G1."
),
},
# --- Certificateur Forgeron : Sécurité et conformité ---
{
"position": "C1",
"item_type": "clause",
"title": "Intention du certifié questionnée",
"sort_order": 19,
"current_text": (
"J'ai questionné l'intention du certifié à rejoindre "
"les forgerons et vérifié sa motivation."
),
},
{
"position": "C2",
"item_type": "verification",
"title": "Pratiques de sécurité du certifié",
"sort_order": 20,
"current_text": (
"J'ai demandé au certifié quelles étaient ses pratiques de "
"sécurité système et réseau."
),
},
{
"position": "C3",
"item_type": "verification",
"title": "Phrase aléatoire du certifié",
"sort_order": 21,
"current_text": (
"Le certifié m'assure que son compte forgeron est issu d'une "
"phrase générée aléatoirement."
),
},
{
"position": "C4",
"item_type": "verification",
"title": "Sauvegarde du certifié",
"sort_order": 22,
"current_text": (
"Le certifié m'assure avoir stocké sa phrase de récupération "
"sur plusieurs supports physiques."
),
},
{
"position": "C5",
"item_type": "verification",
"title": "Noeud du certifié vérifié",
"sort_order": 23,
"current_text": (
"J'ai vérifié que le certifié gère déjà un noeud à jour, "
"correctement synchronisé et joignable."
),
},
{
"position": "C6",
"item_type": "clause",
"title": "Configuration du certifié notée",
"sort_order": 24,
"current_text": (
"J'ai noté le style de configuration du noeud du certifié "
"(matériel, OS, hébergement)."
),
},
{
"position": "C7",
"item_type": "clause",
"title": "Engagement d'information du certifié",
"sort_order": 25,
"current_text": (
"Le certifié s'est engagé à m'informer de tout changement "
"significatif de sa configuration."
),
},
{
"position": "C8",
"item_type": "verification",
"title": "Risques offline connus du certifié",
"sort_order": 26,
"current_text": (
"J'ai vérifié avec le certifié qu'il connaît les risques "
"d'être déclaré offline et les conséquences."
),
},
# --- Certificateur Forgeron : Contact ---
{
"position": "C9",
"item_type": "clause",
"title": "Joindre les certifiés",
"sort_order": 27,
"current_text": (
"Je sais joindre efficacement les forgerons que j'ai certifiés."
),
},
{
"position": "C10",
"item_type": "clause",
"title": "Deux canaux de contact",
"sort_order": 28,
"current_text": (
"Je peux les joindre par au moins 2 canaux différents."
),
},
{
"position": "C11",
"item_type": "clause",
"title": "Contact sous 24h en cas de défaut",
"sort_order": 29,
"current_text": (
"Je m'engage à contacter sous 24h ce forgeron si un défaut "
"concerne son noeud."
),
},
# --- Certificateur Forgeron : Connaissances ---
{
"position": "C12",
"item_type": "verification",
"title": "Engagements acceptés par le certifié",
"sort_order": 30,
"current_text": (
"J'ai vérifié que le certifié a accepté les engagements "
"forgerons intégralement."
),
},
{
"position": "C13",
"item_type": "verification",
"title": "Règles consultables par le certifié",
"sort_order": 31,
"current_text": (
"J'ai vérifié que le certifié sait où consulter les règles "
"détaillées de la TdC forgeron."
),
},
{
"position": "C14",
"item_type": "verification",
"title": "Délais connus du certifié",
"sort_order": 32,
"current_text": (
"J'ai vérifié que le certifié connaît les délais de passage "
"en ligne et hors ligne."
),
},
# --- Certificateur Forgeron : Pièges (expected: NON) ---
{
"position": "C15",
"item_type": "rule",
"title": "Piège : certification sous pression",
"sort_order": 33,
"current_text": (
"[Piège - réponse attendue : NON] "
"Je certifie sous la menace ou autre forme de pression."
),
},
{
"position": "C16",
"item_type": "rule",
"title": "Piège : avantage personnel",
"sort_order": 34,
"current_text": (
"[Piège - réponse attendue : NON] "
"Je tire un avantage personnel en échange de ma certification."
),
},
]
async def seed_document_engagement_forgeron(session: AsyncSession) -> Document:
doc, created = await get_or_create(
session,
Document,
"slug",
"engagement-forgeron",
title="Acte d'engagement forgeron",
doc_type="engagement",
version="2.0.0",
status="active",
description=(
"Acte d'engagement des forgerons (validateurs) Duniter V2. "
"Adopté en février 2026 (97 pour / 23 contre). "
"34 clauses : aspirant (18) + certificateur (16)."
),
)
print(f" Document 'Acte d'engagement forgeron': {'created' if created else 'exists'}")
if created:
for item_data in ENGAGEMENT_FORGERON_ITEMS:
item = DocumentItem(document_id=doc.id, **item_data)
session.add(item)
await session.flush()
print(f" -> {len(ENGAGEMENT_FORGERON_ITEMS)} items created")
return doc
# ---------------------------------------------------------------------------
# Seed: Decision - Runtime Upgrade
# ---------------------------------------------------------------------------
RUNTIME_UPGRADE_STEPS: list[dict] = [
{
"step_order": 1,
"step_type": "qualification",
"title": "Qualification",
"description": (
"Définir le changement : spécification technique, impact sur le "
"réseau, justification. Identifier les risques et dépendances."
),
},
{
"step_order": 2,
"step_type": "review",
"title": "Revue",
"description": (
"Audit technique par le Comité Technique et les forgerons. "
"Revue du code, tests sur testnet, validation de la compatibilité."
),
},
{
"step_order": 3,
"step_type": "vote",
"title": "Vote",
"description": (
"Vote communautaire selon le protocole de vote en vigueur. "
"Le quorum et le seuil d'adoption dépendent de la formule configurée."
),
},
{
"step_order": 4,
"step_type": "execution",
"title": "Exécution",
"description": (
"Mise à jour on-chain via un extrinsic autorisé. "
"Coordination avec les forgerons pour la synchronisation des noeuds."
),
},
{
"step_order": 5,
"step_type": "reporting",
"title": "Suivi",
"description": (
"Surveillance post-upgrade : monitoring des métriques réseau, "
"détection d'anomalies, rapport de stabilité sous 7 jours."
),
},
]
async def seed_decision_runtime_upgrade(session: AsyncSession) -> Decision:
decision, created = await get_or_create(
session,
Decision,
"title",
"Runtime Upgrade",
description=(
"Processus de mise à jour du runtime Duniter V2 on-chain. "
"Chaque upgrade suit un protocole strict en 5 étapes. "
"(Décision on-chain)"
),
decision_type="runtime_upgrade",
status="draft",
)
print(f" Decision 'Runtime Upgrade': {'created' if created else 'exists'}")
if created:
for step_data in RUNTIME_UPGRADE_STEPS:
step = DecisionStep(decision_id=decision.id, **step_data)
session.add(step)
await session.flush()
print(f" -> {len(RUNTIME_UPGRADE_STEPS)} steps created")
return decision
# ---------------------------------------------------------------------------
# Seed: Simulated voters + votes on first 3 engagement items
# ---------------------------------------------------------------------------
VOTER_NAMES = [
"Moul", "Poka", "Hugo", "Elois", "Cgeek",
"Galuel", "Tortue", "Inso", "Tuxmain", "Matograine",
"Maaltir",
]
async def seed_voters(session: AsyncSession) -> list[DuniterIdentity]:
"""Create 11 simulated Duniter identities for voting demo."""
voters: list[DuniterIdentity] = []
for i, name in enumerate(VOTER_NAMES):
# Deterministic address from name
addr_hash = hashlib.sha256(name.encode()).hexdigest()[:32]
address = f"5{addr_hash[:47]}"
voter, created = await get_or_create(
session,
DuniterIdentity,
"address",
address,
display_name=name,
wot_status="member",
is_smith=(i < 5), # First 5 are smiths
)
if created:
print(f" Voter '{name}': created")
voters.append(voter)
return voters
async def seed_votes_on_items(
session: AsyncSession,
doc: Document,
protocol: VotingProtocol,
voters: list[DuniterIdentity],
):
"""Create vote sessions on first 3 items with 10 for + 1 against."""
# Fetch items for this document
stmt = select(DocumentItem).where(
DocumentItem.document_id == doc.id
).order_by(DocumentItem.sort_order).limit(3)
result = await session.execute(stmt)
items = result.scalars().all()
if not items:
print(" No items found to vote on")
return
now = datetime.now(timezone.utc)
for item in items:
# Check if a session already exists for this item
existing_stmt = select(VoteSession).where(
VoteSession.item_version_id == None,
VoteSession.voting_protocol_id == protocol.id,
)
# We'll use a simpler idempotency check
session_id = uuid.uuid5(uuid.NAMESPACE_URL, f"seed-vote-{item.id}")
check_stmt = select(VoteSession).where(VoteSession.id == session_id)
check_result = await session.execute(check_stmt)
if check_result.scalar_one_or_none() is not None:
print(f" VoteSession for '{item.title}': exists")
continue
vote_session = VoteSession(
id=session_id,
decision_id=None,
item_version_id=None,
voting_protocol_id=protocol.id,
wot_size=7224,
smith_size=23,
techcomm_size=5,
starts_at=now - timedelta(days=15),
ends_at=now + timedelta(days=15),
status="open",
votes_for=10,
votes_against=1,
votes_total=11,
threshold_required=97.0,
)
session.add(vote_session)
await session.flush()
# 10 votes "for"
for voter in voters[:10]:
payload = f"vote:{vote_session.id}:{voter.id}:for"
vote = Vote(
session_id=vote_session.id,
voter_id=voter.id,
vote_value="for",
comment="Oui c'est mieux que l'existant",
signature=fake_signature(payload),
signed_payload=payload,
voter_wot_status="member",
voter_is_smith=voter.is_smith,
)
session.add(vote)
# 1 vote "against"
against_voter = voters[10]
payload = f"vote:{vote_session.id}:{against_voter.id}:against"
vote = Vote(
session_id=vote_session.id,
voter_id=against_voter.id,
vote_value="against",
comment="Non, on ne remplace pas tel quel",
signature=fake_signature(payload),
signed_payload=payload,
voter_wot_status="member",
voter_is_smith=False,
)
session.add(vote)
await session.flush()
print(f" VoteSession for '{item.title}': created (10 pour, 1 contre)")
# ---------------------------------------------------------------------------
# Main seed runner
# ---------------------------------------------------------------------------
async def run_seed():
print("=" * 60)
print("Glibredecision - Seed Database")
print("=" * 60)
# Ensure tables exist
await init_db()
print("[0/7] Tables created.\n")
async with async_session() as session:
async with session.begin():
print("\n[1/7] Formula Configs...")
formulas = await seed_formula_configs(session)
print("\n[2/7] Voting Protocols...")
protocols = await seed_voting_protocols(session, formulas)
print("\n[3/7] Document: Acte d'engagement certification...")
await seed_document_engagement_certification(session, protocols)
print("\n[4/7] Document: Acte d'engagement forgeron v2.0.0...")
doc_forgeron = await seed_document_engagement_forgeron(session)
print("\n[5/7] Decision: Runtime Upgrade...")
await seed_decision_runtime_upgrade(session)
print("\n[6/7] Simulated voters...")
voters = await seed_voters(session)
print("\n[7/7] Votes on first 3 engagements forgeron...")
await seed_votes_on_items(
session,
doc_forgeron,
protocols["Vote WoT standard"],
voters,
)
print("\n" + "=" * 60)
print("Seed complete.")
print("=" * 60)
if __name__ == "__main__":
asyncio.run(run_seed())