Files
decision/backend/seed.py
Yvv 62808b974d Composants engagement: GenesisBlock, InertiaSlider, MiniVoteBoard, EngagementCard, DocumentTuto
Backend: genesis_json sur Document, section_tag/inertia_preset/is_permanent_vote sur DocumentItem
Frontend: 5 nouveaux composants pour vue detail document enrichie
- GenesisBlock: sources, outils, synthese forum, contributeurs (depliable)
- InertiaSlider: visualisation inertie 4 niveaux avec params formule G/M
- MiniVoteBoard: tableau vote compact (barre seuil, pour/contre, participation)
- EngagementCard: carte item enrichie integrant vote + inertie + actions
- DocumentTuto: modal pedagogique vote permanent/inertie/seuils
Seed et page [slug] enrichis pour exploiter les nouveaux champs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 07:59:05 +01:00

1480 lines
52 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
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 a inertie basse pour les annexes et recommandations. "
"Gradient faible = plus facile a remplacer meme a 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 a inertie haute pour la section formule elle-meme. "
"Gradient eleve = necessite une forte mobilisation pour changer."
),
"duration_days": 30,
"majority_pct": 60,
"base_exponent": 0.1,
"gradient_exponent": 0.4,
"constant_base": 0.0,
},
"Inertie tres haute (Meta-reglage)": {
"description": (
"Formule a inertie maximale pour le reglage de l'inertie elle-meme. "
"Quasi-unanimite requise a toute participation. Protection contre "
"la modification des regles 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 critere 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,
},
"Comite Tech": {
"description": "Vote avec critere Comite 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 nuance a 6 niveaux (CONTRE..TOUT A 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 a tous les membres de la "
"toile de confiance. Le seuil d'adoption s'adapte au taux de "
"participation via l'inertie WoT : quasi-unanimite a faible "
"participation, majorite simple a forte mobilisation. "
"Utilise 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 critere : seuil WoT standard + "
"seuil minimal de forgerons. Garantit que toute decision "
"impliquant les validateurs soit soutenue par les "
"operateurs du reseau. Utilise pour les engagements "
"forgerons et les decisions d'infrastructure."
),
"vote_type": "binary",
"formula_config_id": formulas["Forgeron avec Smith"].id,
"mode_params": "D30M50B.1G.2S.1",
},
"Vote Comite Technique": {
"description": (
"Vote binaire avec critere TechComm obligatoire. "
"Reserve aux decisions techniques critiques : runtime "
"upgrades, modifications de parametres on-chain, "
"approbation de code. Le Comite Technique doit atteindre "
"un seuil minimal independamment du vote communautaire."
),
"vote_type": "binary",
"formula_config_id": formulas["Comite 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",
},
"reference_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 (rejetee)",
"url": "https://forum.monnaie-libre.fr/t/proposition-charte-1-0/31066",
"status": "rejected",
"posts": 70,
},
{
"title": "Preparation 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": "Regles 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 nuance licence",
"url": "https://forum.monnaie-libre.fr/t/processus-de-validation-licence-par-vote-nuance/31729",
"status": "reference",
},
],
"formula_trigger": (
"Quand un item atteint le seuil d'adoption (formule WoT), "
"le texte de remplacement est integre au document officiel. "
"Le hash IPFS du document mis a jour est ancre on-chain via system.remark. "
"Les applications (Cesium, Gecko) pointent vers le depot git officiel "
"qui est synchronise avec l'etat valide par les votes."
),
"contributors": [
{"name": "1000i100", "role": "Pilote principal, redacteur"},
{"name": "Natha", "role": "Co-redactrice"},
{"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": "Preambule",
"sort_order": 1,
"section_tag": "introduction",
"inertia_preset": "standard",
"current_text": (
"Le present acte d'engagement definit les obligations reciproques "
"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 ete etudie, compris et accepte par le certifie."
),
},
{
"position": "I2",
"item_type": "preamble",
"title": "Les deux garanties reciproques",
"sort_order": 2,
"section_tag": "introduction",
"inertia_preset": "standard",
"current_text": (
"La certification repose sur deux garanties reciproques :\n\n"
"**1.** Derriere une cle publique creatrice de monnaie "
"se trouve un **etre humain vivant**.\n\n"
"**2.** Derriere cet etre humain se trouve **une seule et unique** "
"cle publique creatrice de monnaie.\n\n"
"La certification est un acte technique et fiduciaire, "
"pas un acte d'adhesion 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 "
"irreductible 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": "Unicite du compte",
"sort_order": 4,
"section_tag": "fondamental",
"inertia_preset": "standard",
"current_text": (
"Je m'engage sur l'honneur a n'avoir et n'avoir jamais "
"qu'un seul et unique compte cocreateur de monnaie G1."
),
},
{
"position": "E2",
"item_type": "clause",
"title": "Certification responsable",
"sort_order": 5,
"section_tag": "fondamental",
"inertia_preset": "standard",
"current_text": (
"Je m'engage sur l'honneur a ne certifier que des personnes "
"physiques qui respectent scrupuleusement ces deux presents "
"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 definissent les obligations "
"pratiques et verifiables du certificateur pour garantir "
"la qualite 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 assure de connaitre suffisamment la personne "
"qui gere cette cle publique. Connaitre suffisamment ne signifie "
"pas « avoir vu » ; c'est assurer a la communaute G1 que je "
"pourrai la contacter facilement et etre en mesure de reperer "
"un double-compte ou tout autre probleme."
),
},
{
"position": "E4",
"item_type": "clause",
"title": "Verification personnelle de la cle",
"sort_order": 8,
"section_tag": "technique",
"inertia_preset": "standard",
"current_text": (
"J'ai personnellement verifie que c'est bien cette cle publique "
"que je m'apprete a certifier, en la comparant avec la personne "
"concernee et non par un intermediaire."
),
},
{
"position": "E5",
"item_type": "clause",
"title": "Joignabilite reciproque",
"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 differents "
"et independants (physique, telephone, email, messagerie, etc.)."
),
},
{
"position": "E6",
"item_type": "clause",
"title": "Document de revocation",
"sort_order": 10,
"section_tag": "technique",
"inertia_preset": "standard",
"current_text": (
"J'ai verifie avec la personne certifiee qu'elle a bien genere "
"son document de revocation de compte et qu'elle le conserve "
"en lieu sur."
),
},
{
"position": "E7",
"item_type": "clause",
"title": "Rencontre physique ou verification multi-canaux",
"sort_order": 11,
"section_tag": "technique",
"inertia_preset": "standard",
"current_text": (
"J'ai rencontre la personne physiquement (preferable), **OU** "
"j'ai verifie a distance le lien personne / cle publique par "
"plusieurs moyens de communication differents et independants : "
"courrier + reseau social + forum + email + visio + telephone."
),
},
# ===================================================================
# 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 recommandees pour "
"garantir la qualite et la securite 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 accompagne d'au moins un autre "
"membre de la toile de confiance G1, pour garantir un double "
"controle."
),
},
{
"position": "E9",
"item_type": "clause",
"title": "Verification des certifications existantes",
"sort_order": 14,
"section_tag": "technique",
"inertia_preset": "standard",
"current_text": (
"Avant toute certification, verifiez si le compte a deja "
"recu des certifications et de qui elles proviennent. "
"Contactez les certifieurs existants en cas de doute. "
"Si un certifieurs existant ne connait pas la personne, "
"alertez immediatement les experts de la communaute."
),
},
{
"position": "E10",
"item_type": "clause",
"title": "Verification de maitrise du compte",
"sort_order": 15,
"section_tag": "technique",
"inertia_preset": "standard",
"current_text": (
"Verifiez la maitrise du compte par un transfert-test : "
"envoyez quelques G1 et demandez le renvoi, afin de vous "
"assurer que la personne controle bien sa cle privee."
),
},
{
"position": "E11",
"item_type": "clause",
"title": "Transmission et comprehension du document",
"sort_order": 16,
"section_tag": "technique",
"inertia_preset": "standard",
"current_text": (
"Verifiez que vos contacts ont bien etudie et compris "
"le present acte d'engagement dans sa version a jour "
"avant de proceder a la certification."
),
},
# ===================================================================
# CONCLUSION
# ===================================================================
{
"position": "K1",
"item_type": "preamble",
"title": "Regles abregees de la toile de confiance",
"sort_order": 17,
"section_tag": "conclusion",
"inertia_preset": "standard",
"current_text": (
"**Parametres protocolaires en vigueur :**\n\n"
"- Stock de **100 certifications** possibles\n"
"- 1 certification emissible tous les **5 jours**\n"
"- Nouveau compte valide si **>= 5 certifications** recues en **2 mois**\n"
"- Condition de distance : **<= 5 pas** de **80% des sentinelles**\n"
"- Sentinelle : membre ayant recu et emis >= Y[N] certifs "
"(Y = ceil(N^(1/5)))\n"
"- Certifications actives valables **2 ans**\n"
"- Renouvellement de l'accord tous les **12 mois**"
),
},
{
"position": "K2",
"item_type": "preamble",
"title": "Monnaie G1",
"sort_order": 18,
"section_tag": "conclusion",
"inertia_preset": "standard",
"current_text": (
"**Parametres monetaires :**\n\n"
"- 1 Dividende Universel (DU) par personne par jour\n"
"- Reevaluation a chaque equinoxe : "
"`DU(n+1) = DU(n) + c² × (M/N) / 182.625` avec c = 4.88%\n"
"- DU(0) = 10.00 G1"
),
},
# ===================================================================
# ANNEXE 1 : INTEGRATION LOGICIELLE
# ===================================================================
{
"position": "X1",
"item_type": "section",
"title": "Annexe 1 : Integration logicielle",
"sort_order": 19,
"section_tag": "annexe",
"inertia_preset": "low",
"current_text": (
"Les obligations pour les logiciels implementant la certification G1."
),
},
{
"position": "X1.1",
"item_type": "clause",
"title": "Depot de reference",
"sort_order": 20,
"section_tag": "annexe",
"inertia_preset": "low",
"current_text": (
"Le texte de reference de l'acte d'engagement certification est "
"heberge dans le depot 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 depot 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 present acte d'engagement au certifie et en afficher les "
"parametres 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 utilise pour la certification ou la creation "
"monetaire doit etre publie sous licence libre, afin de "
"permettre son audit par la communaute."
),
},
# ===================================================================
# ANNEXE 2 : QUESTIONS A LA CERTIFICATION (checklist logicielle)
# ===================================================================
{
"position": "X2",
"item_type": "section",
"title": "Annexe 2 : Questions a la certification",
"sort_order": 23,
"section_tag": "annexe",
"inertia_preset": "low",
"current_text": (
"Liste de questions a presenter dans les logiciels de certification. "
"Inspiree de la checklist de la Charte 1.0 et des discussions "
"forum (topic 32412)."
),
},
{
"position": "X2.1",
"item_type": "verification",
"title": "Questions piege (reponse attendue : NON)",
"sort_order": 24,
"section_tag": "annexe",
"inertia_preset": "low",
"current_text": (
"**Si OUI a l'une de ces questions, la certification doit etre refusee.**\n\n"
"- La personne m'a contacte uniquement pour obtenir ma certification\n"
"- Je certifie sous la pression ou pour faire plaisir\n"
"- Je n'ai aucun moyen de verifier l'identite de la personne\n"
"- La personne refuse de me donner plusieurs moyens de contact\n"
"- Je soupçonne que la personne possede deja 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 present acte d'engagement"
),
},
{
"position": "X2.2",
"item_type": "verification",
"title": "Questions identite (reponse attendue : OUI)",
"sort_order": 25,
"section_tag": "annexe",
"inertia_preset": "low",
"current_text": (
"**Si NON a l'une de ces questions, la certification doit etre refusee.**\n\n"
"- Je connais cette personne suffisamment pour la recontacter\n"
"- J'ai verifie personnellement le lien personne / cle publique\n"
"- La personne est une personne physique vivante (pas une entite morale)\n"
"- Je pourrais reconnaitre cette personne si je la croisais\n"
"- J'ai au moins 2 moyens de contact differents pour cette personne"
),
},
{
"position": "X2.3",
"item_type": "verification",
"title": "Questions securite (reponse attendue : OUI)",
"sort_order": 26,
"section_tag": "annexe",
"inertia_preset": "low",
"current_text": (
"**Si NON, la certification est deconseille.**\n\n"
"- La personne a genere son document de revocation\n"
"- La personne maitrise effectivement son compte "
"(test de transfert effectue)\n"
"- La personne sait ou retrouver le present acte d'engagement "
"dans sa version a 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 regit l'adoption ou le rejet de chaque modification "
"du present document. Reference : 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"
"Ou :\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 (electeurs eligibles)\n"
"- **T** = total des votes exprimes (pour + contre)\n"
"- **M** = ratio de majorite cible (M = majority_pct / 100)\n"
"- **G** = exposant du gradient d'inertie\n\n"
"**Comportement :** Faible participation → quasi-unanimite requise. "
"Forte participation → majorite simple M suffit."
),
},
{
"position": "F1.2",
"item_type": "rule",
"title": "Parametres par defaut",
"sort_order": 29,
"section_tag": "formule",
"inertia_preset": "high",
"current_text": (
"Parametres de reference pour les engagements (inertie standard) :\n\n"
"| Parametre | Code | Valeur |\n"
"|-----------|------|--------|\n"
"| Duree | D | 30 jours (permanent) |\n"
"| Majorite | M | 50% |\n"
"| Base | B | 0.1 |\n"
"| Gradient | G | 0.2 |\n"
"| Constante | C | 0.0 |\n\n"
"Mode compact : **D30M50B.1G.2**"
),
},
{
"position": "F1.3",
"item_type": "rule",
"title": "Processus de depot 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 integre au document officiel\n"
"2. Le hash IPFS du document mis a jour est calcule\n"
"3. Le hash est ancre on-chain via `system.remark`\n"
"4. Le depot git officiel est synchronise\n"
"5. Les applications (Cesium, Gecko) mettent a jour automatiquement"
),
},
# ===================================================================
# SECTION SPECIALE : REGLAGE DE L'INERTIE
# ===================================================================
{
"position": "N1",
"item_type": "section",
"title": "Reglage de l'inertie",
"sort_order": 31,
"section_tag": "inertie",
"inertia_preset": "very_high",
"current_text": (
"Le reglage de l'inertie definit la difficulte de remplacement "
"de chaque section du document. Ce reglage est lui-meme soumis "
"a l'inertie la plus elevee, pour empecher la modification "
"des regles de modification."
),
},
{
"position": "N1.1",
"item_type": "rule",
"title": "Niveaux d'inertie",
"sort_order": 32,
"section_tag": "inertie",
"inertia_preset": "very_high",
"current_text": (
"| Niveau | Gradient G | Majorite M | Application |\n"
"|--------|-----------|------------|-------------|\n"
"| **Basse** | 0.1 | 50% | Annexes, recommandations |\n"
"| **Standard** | 0.2 | 50% | Engagements fondamentaux et techniques |\n"
"| **Haute** | 0.4 | 60% | Formule de vote |\n"
"| **Tres haute** | 0.6 | 66% | Reglage de l'inertie |\n\n"
"Plus le gradient est eleve, plus il faut de participation "
"et de consensus pour qu'une modification soit adoptee."
),
},
# ===================================================================
# SECTION SPECIALE : ORDONNANCEMENT
# ===================================================================
{
"position": "O1",
"item_type": "section",
"title": "Ordonnancement du document",
"sort_order": 33,
"section_tag": "ordonnancement",
"inertia_preset": "high",
"current_text": (
"L'ordre de presentation des items dans le document est "
"lui-meme soumis au vote. Toute proposition de reorganisation "
"doit atteindre le seuil d'adoption avec l'inertie haute."
),
},
]
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 etre "
"remplace par une alternative qui atteint le seuil d'adoption. "
"Construit a 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 : Securite et conformite ---
{
"position": "A1",
"item_type": "clause",
"title": "Intention et motivation",
"sort_order": 1,
"current_text": (
"J'ai clarifie ce qui me motive a devenir forgeron, "
"j'en assume les raisons."
),
},
{
"position": "A2",
"item_type": "clause",
"title": "Veille securite",
"sort_order": 2,
"current_text": (
"Je fais de la veille pour maintenir mes pratiques de securite "
"systeme et reseau a jour."
),
},
{
"position": "A3",
"item_type": "clause",
"title": "Notifications forum",
"sort_order": 3,
"current_text": (
"J'ai active les notifications sur forum.duniter.org pour etre "
"alerte des discussions importantes concernant le reseau."
),
},
{
"position": "A4",
"item_type": "verification",
"title": "Phrase de recuperation aleatoire",
"sort_order": 4,
"current_text": (
"Je confirme que ma phrase de recuperation a ete generee "
"aleatoirement et n'est pas une phrase choisie par moi."
),
},
{
"position": "A5",
"item_type": "verification",
"title": "Compte separe",
"sort_order": 5,
"current_text": (
"J'utilise un autre compte pour mes transactions courantes ; "
"le compte forgeron est strictement reserve a la validation."
),
},
{
"position": "A6",
"item_type": "verification",
"title": "Sauvegarde phrase de recuperation",
"sort_order": 6,
"current_text": (
"J'ai stocke ma phrase de recuperation sur plusieurs supports "
"physiques distincts et securises."
),
},
{
"position": "A7",
"item_type": "verification",
"title": "Noeud a jour et synchronise",
"sort_order": 7,
"current_text": (
"Je gere deja un noeud a jour, correctement synchronise et "
"joignable par les autres noeuds du reseau."
),
},
{
"position": "A8",
"item_type": "verification",
"title": "API unsafe non exposee",
"sort_order": 8,
"current_text": (
"J'ai veille a 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 a la demande d'un autre forgeron, mes choix "
"techniques (materiel, OS, configuration reseau)."
),
},
{
"position": "A10",
"item_type": "clause",
"title": "Declaration offline en cas de doute",
"sort_order": 10,
"current_text": (
"Je me declare offline en cas de doute sur la securite "
"de mon noeud ou de mon infrastructure."
),
},
{
"position": "A11",
"item_type": "clause",
"title": "Reactivite 24h",
"sort_order": 11,
"current_text": (
"Je m'engage a repondre en moins de 24h aux forgerons "
"quand je suis declare 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 differents."
),
},
# --- 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": "Regles de la TdC forgeron",
"sort_order": 14,
"current_text": (
"J'ai pris connaissance des regles et delais associes 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 reseau blockchain "
"Duniter et le role du validateur."
),
},
# --- Aspirant Forgeron : Pieges (expected: NON) ---
{
"position": "A16",
"item_type": "rule",
"title": "Piege : harcelement",
"sort_order": 16,
"current_text": (
"[Piege - reponse attendue : NON] "
"J'insiste, harcele ou fais pression pour etre certifie forgeron."
),
},
{
"position": "A17",
"item_type": "rule",
"title": "Piege : gloire et pouvoir",
"sort_order": 17,
"current_text": (
"[Piege - reponse attendue : NON] "
"Je veux etre forgeron pour la gloire et le pouvoir."
),
},
{
"position": "A18",
"item_type": "rule",
"title": "Piege : nuisance",
"sort_order": 18,
"current_text": (
"[Piege - reponse attendue : NON] "
"Je cherche a nuire a l'ecosysteme G1."
),
},
# --- Certificateur Forgeron : Securite et conformite ---
{
"position": "C1",
"item_type": "clause",
"title": "Intention du certifie questionnee",
"sort_order": 19,
"current_text": (
"J'ai questionne l'intention du certifie a rejoindre "
"les forgerons et verifie sa motivation."
),
},
{
"position": "C2",
"item_type": "verification",
"title": "Pratiques de securite du certifie",
"sort_order": 20,
"current_text": (
"J'ai demande au certifie quelles etaient ses pratiques de "
"securite systeme et reseau."
),
},
{
"position": "C3",
"item_type": "verification",
"title": "Phrase aleatoire du certifie",
"sort_order": 21,
"current_text": (
"Le certifie m'assure que son compte forgeron est issu d'une "
"phrase generee aleatoirement."
),
},
{
"position": "C4",
"item_type": "verification",
"title": "Sauvegarde du certifie",
"sort_order": 22,
"current_text": (
"Le certifie m'assure avoir stocke sa phrase de recuperation "
"sur plusieurs supports physiques."
),
},
{
"position": "C5",
"item_type": "verification",
"title": "Noeud du certifie verifie",
"sort_order": 23,
"current_text": (
"J'ai verifie que le certifie gere deja un noeud a jour, "
"correctement synchronise et joignable."
),
},
{
"position": "C6",
"item_type": "clause",
"title": "Configuration du certifie notee",
"sort_order": 24,
"current_text": (
"J'ai note le style de configuration du noeud du certifie "
"(materiel, OS, hebergement)."
),
},
{
"position": "C7",
"item_type": "clause",
"title": "Engagement d'information du certifie",
"sort_order": 25,
"current_text": (
"Le certifie s'est engage a m'informer de tout changement "
"significatif de sa configuration."
),
},
{
"position": "C8",
"item_type": "verification",
"title": "Risques offline connus du certifie",
"sort_order": 26,
"current_text": (
"J'ai verifie avec le certifie qu'il connait les risques "
"d'etre declare offline et les consequences."
),
},
# --- Certificateur Forgeron : Contact ---
{
"position": "C9",
"item_type": "clause",
"title": "Joindre les certifies",
"sort_order": 27,
"current_text": (
"Je sais joindre efficacement les forgerons que j'ai certifies."
),
},
{
"position": "C10",
"item_type": "clause",
"title": "Deux canaux de contact",
"sort_order": 28,
"current_text": (
"Je peux les joindre par au moins 2 canaux differents."
),
},
{
"position": "C11",
"item_type": "clause",
"title": "Contact sous 24h en cas de defaut",
"sort_order": 29,
"current_text": (
"Je m'engage a contacter sous 24h ce forgeron si un defaut "
"concerne son noeud."
),
},
# --- Certificateur Forgeron : Connaissances ---
{
"position": "C12",
"item_type": "verification",
"title": "Engagements acceptes par le certifie",
"sort_order": 30,
"current_text": (
"J'ai verifie que le certifie a accepte les engagements "
"forgerons integralement."
),
},
{
"position": "C13",
"item_type": "verification",
"title": "Regles consultables par le certifie",
"sort_order": 31,
"current_text": (
"J'ai verifie que le certifie sait ou consulter les regles "
"detaillees de la TdC forgeron."
),
},
{
"position": "C14",
"item_type": "verification",
"title": "Delais connus du certifie",
"sort_order": 32,
"current_text": (
"J'ai verifie que le certifie connait les delais de passage "
"en ligne et hors ligne."
),
},
# --- Certificateur Forgeron : Pieges (expected: NON) ---
{
"position": "C15",
"item_type": "rule",
"title": "Piege : certification sous pression",
"sort_order": 33,
"current_text": (
"[Piege - reponse attendue : NON] "
"Je certifie sous la menace ou autre forme de pression."
),
},
{
"position": "C16",
"item_type": "rule",
"title": "Piege : avantage personnel",
"sort_order": 34,
"current_text": (
"[Piege - reponse attendue : NON] "
"Je tire un avantage personnel en echange 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. "
"Adopte en fevrier 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": (
"Definir le changement : specification technique, impact sur le "
"reseau, justification. Identifier les risques et dependances."
),
},
{
"step_order": 2,
"step_type": "review",
"title": "Revue",
"description": (
"Audit technique par le Comite Technique et les forgerons. "
"Revue du code, tests sur testnet, validation de la compatibilite."
),
},
{
"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 dependent de la formule configuree."
),
},
{
"step_order": 4,
"step_type": "execution",
"title": "Execution",
"description": (
"Mise a jour on-chain via un extrinsic autorise. "
"Coordination avec les forgerons pour la synchronisation des noeuds."
),
},
{
"step_order": 5,
"step_type": "reporting",
"title": "Suivi",
"description": (
"Surveillance post-upgrade : monitoring des metriques reseau, "
"detection d'anomalies, rapport de stabilite 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 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 '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)
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())