v3 : socle Rust (moteur pur, accès trustinWallet, serveur) ; maquette Nuxt rangée dans maquette/

- crates/ld-engine : formule inertielle (parité avec la maquette, garde C),
  coquille / changement de fond, assistant d'édition à règles, composition
  d'un document sous vote permanent (adoptions, aperçus, rivales).
- crates/trustin-access : qui voit, qui ouvre, qui gère les accès.
- crates/ld-server : API axum + SQLite, connexion Ğ1 (client OAuth2 de
  connect.monnaie-libre.fr), invitations, entrée en vigueur automatique,
  signalements de contradiction, traductions éditoriales, publication git,
  taille de la toile lue sur la chaîne.
- seeds/acte-engagement-certification.json : 33 articles, fr + en + es ;
  en vigueur : préambule, E1, E2 (seed a minima).
- maquette/ : frontend, backend et docker v2 déplacés tels quels.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Yvv
2026-10-21 02:04:42 +02:00
co-authored by Claude Opus 5.5
parent 4b1732916d
commit 67ddee28b2
275 changed files with 8979 additions and 0 deletions
@@ -0,0 +1,301 @@
"""initial schema
Revision ID: 0b9c1d2e3f4a
Revises:
Create Date: 2026-04-23 00:00:00.000000+00:00
Idempotent: uses CREATE TABLE IF NOT EXISTS so it is safe to run
against a DB that was already bootstrapped via SQLAlchemy create_all().
"""
from typing import Sequence, Union
from alembic import op
revision: str = '0b9c1d2e3f4a'
down_revision: Union[str, None] = None
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
op.execute("""
CREATE TABLE IF NOT EXISTS duniter_identities (
id UUID PRIMARY KEY,
address VARCHAR(64) NOT NULL,
display_name VARCHAR(128),
wot_status VARCHAR(32) NOT NULL DEFAULT 'unknown',
is_smith BOOLEAN NOT NULL DEFAULT FALSE,
is_techcomm BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
)
""")
op.execute("CREATE UNIQUE INDEX IF NOT EXISTS ix_duniter_identities_address ON duniter_identities (address)")
op.execute("""
CREATE TABLE IF NOT EXISTS sessions (
id UUID PRIMARY KEY,
token_hash VARCHAR(128) NOT NULL,
identity_id UUID NOT NULL REFERENCES duniter_identities(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
expires_at TIMESTAMPTZ NOT NULL
)
""")
op.execute("CREATE UNIQUE INDEX IF NOT EXISTS ix_sessions_token_hash ON sessions (token_hash)")
op.execute("""
CREATE TABLE IF NOT EXISTS organizations (
id UUID PRIMARY KEY,
name VARCHAR(128) NOT NULL,
slug VARCHAR(128) NOT NULL,
org_type VARCHAR(64) NOT NULL DEFAULT 'community',
is_transparent BOOLEAN NOT NULL DEFAULT FALSE,
color VARCHAR(32),
icon VARCHAR(64),
description TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
)
""")
op.execute("CREATE UNIQUE INDEX IF NOT EXISTS ix_organizations_slug ON organizations (slug)")
op.execute("""
CREATE TABLE IF NOT EXISTS org_members (
id UUID PRIMARY KEY,
org_id UUID NOT NULL REFERENCES organizations(id),
identity_id UUID NOT NULL REFERENCES duniter_identities(id),
role VARCHAR(32) NOT NULL DEFAULT 'member',
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
)
""")
op.execute("""
CREATE TABLE IF NOT EXISTS formula_configs (
id UUID PRIMARY KEY,
name VARCHAR(128) NOT NULL,
description TEXT,
duration_days INTEGER NOT NULL DEFAULT 30,
majority_pct INTEGER NOT NULL DEFAULT 50,
base_exponent FLOAT NOT NULL DEFAULT 0.1,
gradient_exponent FLOAT NOT NULL DEFAULT 0.2,
constant_base FLOAT NOT NULL DEFAULT 0.0,
smith_exponent FLOAT,
techcomm_exponent FLOAT,
nuanced_min_participants INTEGER,
nuanced_threshold_pct INTEGER,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
)
""")
op.execute("""
CREATE TABLE IF NOT EXISTS voting_protocols (
id UUID PRIMARY KEY,
name VARCHAR(128) NOT NULL,
description TEXT,
vote_type VARCHAR(32) NOT NULL,
formula_config_id UUID NOT NULL REFERENCES formula_configs(id),
mode_params VARCHAR(64),
is_meta_governed BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
)
""")
op.execute("""
CREATE TABLE IF NOT EXISTS sanctuary_entries (
id UUID PRIMARY KEY,
entry_type VARCHAR(64) NOT NULL,
reference_id UUID NOT NULL,
title VARCHAR(256),
content_hash VARCHAR(128) NOT NULL,
ipfs_cid VARCHAR(128),
chain_tx_hash VARCHAR(128),
chain_block INTEGER,
metadata_json TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
)
""")
op.execute("""
CREATE TABLE IF NOT EXISTS blockchain_cache (
id UUID PRIMARY KEY,
cache_key VARCHAR(256) NOT NULL,
cache_value JSON NOT NULL,
fetched_at TIMESTAMPTZ NOT NULL DEFAULT now(),
expires_at TIMESTAMPTZ NOT NULL
)
""")
op.execute("CREATE UNIQUE INDEX IF NOT EXISTS ix_blockchain_cache_cache_key ON blockchain_cache (cache_key)")
op.execute("""
CREATE TABLE IF NOT EXISTS documents (
id UUID PRIMARY KEY,
slug VARCHAR(128) NOT NULL,
title VARCHAR(256) NOT NULL,
doc_type VARCHAR(64) NOT NULL,
version VARCHAR(32) NOT NULL DEFAULT '0.1.0',
status VARCHAR(32) NOT NULL DEFAULT 'draft',
description TEXT,
ipfs_cid VARCHAR(128),
chain_anchor VARCHAR(128),
genesis_json TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
)
""")
op.execute("CREATE UNIQUE INDEX IF NOT EXISTS ix_documents_slug ON documents (slug)")
op.execute("""
CREATE TABLE IF NOT EXISTS decisions (
id UUID PRIMARY KEY,
title VARCHAR(256) NOT NULL,
description TEXT,
context TEXT,
decision_type VARCHAR(64) NOT NULL,
status VARCHAR(32) NOT NULL DEFAULT 'draft',
voting_protocol_id UUID REFERENCES voting_protocols(id),
created_by_id UUID REFERENCES duniter_identities(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
)
""")
op.execute("""
CREATE TABLE IF NOT EXISTS item_versions (
id UUID PRIMARY KEY,
item_id UUID NOT NULL,
proposed_text TEXT NOT NULL,
diff_text TEXT,
rationale TEXT,
status VARCHAR(32) NOT NULL DEFAULT 'proposed',
decision_id UUID REFERENCES decisions(id),
proposed_by_id UUID REFERENCES duniter_identities(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
)
""")
op.execute("""
CREATE TABLE IF NOT EXISTS vote_sessions (
id UUID PRIMARY KEY,
decision_id UUID REFERENCES decisions(id),
item_version_id UUID REFERENCES item_versions(id),
voting_protocol_id UUID NOT NULL REFERENCES voting_protocols(id),
wot_size INTEGER NOT NULL DEFAULT 0,
smith_size INTEGER NOT NULL DEFAULT 0,
techcomm_size INTEGER NOT NULL DEFAULT 0,
starts_at TIMESTAMPTZ NOT NULL DEFAULT now(),
ends_at TIMESTAMPTZ NOT NULL,
status VARCHAR(32) NOT NULL DEFAULT 'open',
votes_for INTEGER NOT NULL DEFAULT 0,
votes_against INTEGER NOT NULL DEFAULT 0,
votes_total INTEGER NOT NULL DEFAULT 0,
smith_votes_for INTEGER NOT NULL DEFAULT 0,
techcomm_votes_for INTEGER NOT NULL DEFAULT 0,
threshold_required FLOAT NOT NULL DEFAULT 0.0,
result VARCHAR(32),
chain_recorded BOOLEAN NOT NULL DEFAULT FALSE,
chain_tx_hash VARCHAR(128),
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
)
""")
op.execute("""
CREATE TABLE IF NOT EXISTS decision_steps (
id UUID PRIMARY KEY,
decision_id UUID NOT NULL REFERENCES decisions(id),
step_order INTEGER NOT NULL,
step_type VARCHAR(32) NOT NULL,
title VARCHAR(256),
description TEXT,
status VARCHAR(32) NOT NULL DEFAULT 'pending',
vote_session_id UUID REFERENCES vote_sessions(id),
outcome TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
)
""")
op.execute("""
CREATE TABLE IF NOT EXISTS document_items (
id UUID PRIMARY KEY,
document_id UUID NOT NULL REFERENCES documents(id),
position VARCHAR(16) NOT NULL,
item_type VARCHAR(32) NOT NULL DEFAULT 'clause',
title VARCHAR(256),
current_text TEXT NOT NULL,
voting_protocol_id UUID REFERENCES voting_protocols(id),
sort_order INTEGER NOT NULL DEFAULT 0,
section_tag VARCHAR(64),
inertia_preset VARCHAR(16) NOT NULL DEFAULT 'standard',
is_permanent_vote BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
)
""")
op.execute("""
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.table_constraints
WHERE constraint_name = 'item_versions_item_id_fkey'
AND table_name = 'item_versions'
) THEN
ALTER TABLE item_versions
ADD CONSTRAINT item_versions_item_id_fkey
FOREIGN KEY (item_id) REFERENCES document_items(id);
END IF;
END $$
""")
op.execute("""
CREATE TABLE IF NOT EXISTS votes (
id UUID PRIMARY KEY,
session_id UUID NOT NULL REFERENCES vote_sessions(id),
voter_id UUID NOT NULL REFERENCES duniter_identities(id),
vote_value VARCHAR(32) NOT NULL,
nuanced_level INTEGER,
comment TEXT,
signature TEXT NOT NULL,
signed_payload TEXT NOT NULL,
voter_wot_status VARCHAR(32) NOT NULL DEFAULT 'member',
voter_is_smith BOOLEAN NOT NULL DEFAULT FALSE,
voter_is_techcomm BOOLEAN NOT NULL DEFAULT FALSE,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
)
""")
op.execute("""
CREATE TABLE IF NOT EXISTS mandates (
id UUID PRIMARY KEY,
title VARCHAR(256) NOT NULL,
description TEXT,
mandate_type VARCHAR(64) NOT NULL,
status VARCHAR(32) NOT NULL DEFAULT 'draft',
mandatee_id UUID REFERENCES duniter_identities(id),
decision_id UUID REFERENCES decisions(id),
starts_at TIMESTAMPTZ,
ends_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
)
""")
op.execute("""
CREATE TABLE IF NOT EXISTS mandate_steps (
id UUID PRIMARY KEY,
mandate_id UUID NOT NULL REFERENCES mandates(id),
step_order INTEGER NOT NULL,
step_type VARCHAR(32) NOT NULL,
title VARCHAR(256),
description TEXT,
status VARCHAR(32) NOT NULL DEFAULT 'pending',
vote_session_id UUID REFERENCES vote_sessions(id),
outcome TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
)
""")
def downgrade() -> None:
# Intentionally left empty — dropping the initial schema would destroy all data.
pass
@@ -0,0 +1,40 @@
"""add organizations
Revision ID: 70914b334cfb
Revises:
Create Date: 2026-04-23 12:27:56.220214+00:00
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
revision: str = '70914b334cfb'
down_revision: Union[str, None] = '0b9c1d2e3f4a'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ADD COLUMN IF NOT EXISTS — idempotent (safe on DBs bootstrapped via create_all)
op.execute("ALTER TABLE decisions ADD COLUMN IF NOT EXISTS organization_id UUID REFERENCES organizations(id)")
op.execute("CREATE INDEX IF NOT EXISTS ix_decisions_organization_id ON decisions (organization_id)")
op.execute("ALTER TABLE documents ADD COLUMN IF NOT EXISTS organization_id UUID REFERENCES organizations(id)")
op.execute("CREATE INDEX IF NOT EXISTS ix_documents_organization_id ON documents (organization_id)")
op.execute("ALTER TABLE mandates ADD COLUMN IF NOT EXISTS organization_id UUID REFERENCES organizations(id)")
op.execute("CREATE INDEX IF NOT EXISTS ix_mandates_organization_id ON mandates (organization_id)")
op.execute("ALTER TABLE voting_protocols ADD COLUMN IF NOT EXISTS organization_id UUID REFERENCES organizations(id)")
op.execute("CREATE INDEX IF NOT EXISTS ix_voting_protocols_organization_id ON voting_protocols (organization_id)")
def downgrade() -> None:
op.drop_index(op.f('ix_voting_protocols_organization_id'), table_name='voting_protocols')
op.drop_column('voting_protocols', 'organization_id')
op.drop_index(op.f('ix_mandates_organization_id'), table_name='mandates')
op.drop_column('mandates', 'organization_id')
op.drop_index(op.f('ix_documents_organization_id'), table_name='documents')
op.drop_column('documents', 'organization_id')
op.drop_index(op.f('ix_decisions_organization_id'), table_name='decisions')
op.drop_column('decisions', 'organization_id')
@@ -0,0 +1,34 @@
"""add_qualification_protocol
Revision ID: b78571ae9e00
Revises: 70914b334cfb
Create Date: 2026-04-23 17:08:07.161306+00:00
"""
from typing import Sequence, Union
import sqlalchemy as sa
from alembic import op
revision: str = 'b78571ae9e00'
down_revision: Union[str, None] = '70914b334cfb'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
op.execute("""
CREATE TABLE IF NOT EXISTS qualification_protocols (
id UUID PRIMARY KEY,
name VARCHAR(128) NOT NULL,
description TEXT,
small_group_max INTEGER NOT NULL DEFAULT 5,
collective_wot_min INTEGER NOT NULL DEFAULT 50,
default_modalities_json TEXT NOT NULL DEFAULT '["vote_wot","vote_smith","consultation_avis","election"]',
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ DEFAULT now()
)
""")
def downgrade() -> None:
op.drop_table('qualification_protocols')
@@ -0,0 +1,48 @@
"""Add groups and group_members tables.
Revision ID: c4e812fb3a01
Revises: b78571ae9e00
Create Date: 2026-04-23 19:00:00.000000
"""
from __future__ import annotations
import sqlalchemy as sa
from alembic import op
revision = "c4e812fb3a01"
down_revision = "b78571ae9e00"
branch_labels = None
depends_on = None
def upgrade() -> None:
op.execute("""
CREATE TABLE IF NOT EXISTS groups (
id UUID PRIMARY KEY,
name VARCHAR(128) NOT NULL,
description TEXT,
organization_id UUID REFERENCES organizations(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
)
""")
op.execute("CREATE INDEX IF NOT EXISTS ix_groups_organization_id ON groups (organization_id)")
op.execute("""
CREATE TABLE IF NOT EXISTS group_members (
id UUID PRIMARY KEY,
group_id UUID NOT NULL REFERENCES groups(id),
identity_id UUID REFERENCES duniter_identities(id),
display_name VARCHAR(128) NOT NULL,
added_at TIMESTAMPTZ NOT NULL DEFAULT now()
)
""")
op.execute("CREATE INDEX IF NOT EXISTS ix_group_members_group_id ON group_members (group_id)")
def downgrade() -> None:
op.drop_index("ix_group_members_group_id", table_name="group_members")
op.drop_table("group_members")
op.drop_index("ix_groups_organization_id", table_name="groups")
op.drop_table("groups")
@@ -0,0 +1,24 @@
"""Add origin column to mandates table.
Revision ID: d91a3c7f8b02
Revises: c4e812fb3a01
Create Date: 2026-04-24 10:00:00.000000
"""
from __future__ import annotations
import sqlalchemy as sa
from alembic import op
revision = "d91a3c7f8b02"
down_revision = "c4e812fb3a01"
branch_labels = None
depends_on = None
def upgrade() -> None:
op.execute("ALTER TABLE mandates ADD COLUMN IF NOT EXISTS origin TEXT")
def downgrade() -> None:
op.drop_column("mandates", "origin")
@@ -0,0 +1,27 @@
"""Mandate origin: replace free-text with FK to duniter_identities.
Revision ID: e3f4a5b6c7d8
Revises: d91a3c7f8b02
Create Date: 2026-04-25 10:00:00.000000
"""
from __future__ import annotations
from alembic import op
revision = "e3f4a5b6c7d8"
down_revision = "d91a3c7f8b02"
branch_labels = None
depends_on = None
def upgrade() -> None:
op.execute("ALTER TABLE mandates DROP COLUMN IF EXISTS origin")
op.execute("ALTER TABLE mandates ADD COLUMN IF NOT EXISTS origin_id UUID REFERENCES duniter_identities(id)")
op.execute("CREATE INDEX IF NOT EXISTS ix_mandates_origin_id ON mandates (origin_id)")
def downgrade() -> None:
op.execute("DROP INDEX IF EXISTS ix_mandates_origin_id")
op.execute("ALTER TABLE mandates DROP COLUMN IF EXISTS origin_id")
op.execute("ALTER TABLE mandates ADD COLUMN IF NOT EXISTS origin TEXT")