/** * Test de NON-PERTE du seed Ğ1 — BLUEPRINT-V2.md « Seeds » (bloquant). * * Vérifie que frontend/app/data/seeds/duniter-g1.bundle.json (émis par * backend/scripts/export_seed_bundle.py depuis backend/seed.py) conserve * l'intégralité du contenu réel Ğ1 : * - 33 clauses Certification + 59 clauses Forgeron, exactement ; * - presets d'inertie conservés (les 4 valeurs présentes sur chaque doc, * répartition exacte du seed v1 : cert 8/19/4/2, forgeron 4/49/4/2) ; * - provenance.voteRecord == { for: 97, against: 23, invalid: 19, * wotSize: 7224, thresholdRequired: 97 }, modeParams 'D30M50B.1G.2S.1' ; * - 11 personnes dont 5 smiths ; 3 mandats ; ≥ 7 protocoles dont le * Consentement (invariant) ; * - chaque clause courante chaînée à sa décision fondatrice adoptée ; * - chaque entité porte collectiveId / createdAt / updatedAt. */ import { describe, expect, it } from 'vitest' import { resolveSettings } from '~/engine/settings' import type { Bundle, Clause, ClauseVersion, Decision, InertiaPreset, Mandate, Person, Protocol, TextDoc, Vote, VoteSession, } from '~/types/domain' import raw from '../../app/data/seeds/duniter-g1.bundle.json' const bundle = raw as unknown as Bundle const docBySlug = (slug: string): TextDoc => { const doc = bundle.docs.find((d) => d.slug === slug) expect(doc, `doc ${slug}`).toBeDefined() return doc as TextDoc } const clausesOf = (doc: TextDoc): Clause[] => bundle.clauses.filter((c) => c.docId === doc.id) const versionById = new Map(bundle.versions.map((v) => [v.id, v])) const decisionById = new Map(bundle.decisions.map((d) => [d.id, d])) const protocolById = new Map(bundle.protocols.map((p) => [p.id, p])) const personByName = new Map(bundle.people.map((p) => [p.displayName, p])) const inertiaDistribution = (doc: TextDoc): Record => { const dist: Record = {} for (const clause of clausesOf(doc)) dist[clause.inertia] = (dist[clause.inertia] ?? 0) + 1 return dist } describe('bundle duniter-g1 — schéma v2', () => { it('est un Bundle schemaVersion 2 avec assents:[]', () => { expect(bundle.schemaVersion).toBe(2) expect(bundle.exportedAt).toBeTruthy() expect(bundle.assents).toEqual([]) expect(bundle.concerns).toEqual([]) expect(bundle.objections).toEqual([]) expect(bundle.advices).toEqual([]) }) it('collectif : slug/template/transparence/couleur/icône de la spec Seeds', () => { const c = bundle.collective expect(c.slug).toBe('duniter-g1') expect(c.template).toBe('free-currency') expect(c.isTransparent).toBe(true) expect(c.color).toBe('#22c55e') expect(c.icon).toBe('i-lucide-network') expect(bundle.docs.find((d) => d.id === c.pactDocId)?.role).toBe('pact') expect(bundle.circles.find((ci) => ci.id === c.rootCircleId)?.name).toBe('Toile de confiance') }) it('chaque entité porte collectiveId, createdAt et updatedAt', () => { const collections = [ bundle.people, bundle.circles, bundle.decisions, bundle.concerns, bundle.objections, bundle.advices, bundle.assents, bundle.mandates, bundle.docs, bundle.clauses, bundle.versions, bundle.protocols, bundle.sessions, bundle.votes, ] as { id: string; collectiveId: string; createdAt: string; updatedAt: string }[][] for (const collection of collections) { for (const item of collection) { expect(item.collectiveId, `collectiveId de ${item.id}`).toBe(bundle.collective.id) expect(item.createdAt, `createdAt de ${item.id}`).toBeTruthy() expect(item.updatedAt, `updatedAt de ${item.id}`).toBeTruthy() } } expect(bundle.collective.createdAt).toBeTruthy() expect(bundle.collective.updatedAt).toBeTruthy() }) }) describe('non-perte — Acte d’engagement Certification (33 items)', () => { const doc = docBySlug('engagement-certification') const clauses = clausesOf(doc) it('contient exactement 33 clauses aux codes uniques', () => { expect(clauses).toHaveLength(33) expect(new Set(clauses.map((c) => c.code)).size).toBe(33) }) it('conserve la répartition des 4 presets d’inertie du seed v1 (8/19/4/2)', () => { expect(inertiaDistribution(doc)).toEqual({ low: 8, standard: 19, high: 4, max: 2 }) }) it('conserve le texte voté (échantillon E1 — unicité du compte)', () => { const e1 = clauses.find((c) => c.code === 'E1') expect(e1).toBeDefined() const version = versionById.get(e1!.currentVersionId!) expect(version?.content).toContain('un seul et unique compte cocréateur de monnaie G1') }) it('porte la provenance v1 (sources + contributeurs)', () => { expect(doc.provenance?.sources.length).toBeGreaterThanOrEqual(6) expect(doc.provenance?.contributors?.join(' ')).toContain('1000i100') }) }) describe('non-perte — Acte d’engagement forgeron (59 items v2.0.0-fr)', () => { const doc = docBySlug('engagement-forgeron') const clauses = clausesOf(doc) it('contient exactement 59 clauses aux codes uniques, en versionLabel 2.0.0-fr', () => { expect(clauses).toHaveLength(59) expect(new Set(clauses.map((c) => c.code)).size).toBe(59) for (const clause of clauses) { expect(versionById.get(clause.currentVersionId!)?.versionLabel).toBe('2.0.0-fr') } }) it('conserve les 18 clauses aspirant (A1..A18) et les 16 certificateur (C1..C16)', () => { const codes = new Set(clauses.map((c) => c.code)) for (let i = 1; i <= 18; i++) expect(codes.has(`A${i}`), `A${i}`).toBe(true) for (let i = 1; i <= 16; i++) expect(codes.has(`C${i}`), `C${i}`).toBe(true) }) it('conserve la répartition des 4 presets d’inertie du seed v1 (4/49/4/2)', () => { expect(inertiaDistribution(doc)).toEqual({ low: 4, standard: 49, high: 4, max: 2 }) }) it('voteRecord === {for:97, against:23, invalid:19, wotSize:7224, thresholdRequired:97}', () => { const record = doc.provenance?.voteRecord expect(record).toBeDefined() expect(record!.result).toMatchObject({ for: 97, against: 23, invalid: 19, wotSize: 7224, thresholdRequired: 97, }) expect(record!.modeParams).toBe('D30M50B.1G.2S.1') expect(record!.period).toBe('2026-01-07 → 2026-02-06') expect(record!.url).toBe( 'https://forum.monnaie-libre.fr/t/vote-engagement-forgeron-v2-0-0/33165', ) }) }) describe('non-perte — Comité Technique et Pacte', () => { it('engagement-comite-tech : clauses présentes, source du vote 33293 tracée', () => { const doc = docBySlug('engagement-comite-tech') const clauses = clausesOf(doc) expect(clauses.length).toBeGreaterThanOrEqual(4) expect(doc.provenance?.sources.some((s) => s.url.includes('33293'))).toBe(true) // Contenu reconstruit (vote en cours au moment de la recherche) — noté : expect(doc.provenance?.notes).toContain('research_duniter_forum.md') }) it('pacte-duniter-g1 : rôle pact, description sacralisée, A1 « Notre finalité »', () => { const pact = docBySlug('pacte-duniter-g1') expect(pact.role).toBe('pact') expect(pact.description).toBe('Notre contrat social — sacralisé, jamais immuable') const a1 = clausesOf(pact).find((c) => c.code === 'A1') expect(a1?.title).toBe('Notre finalité') const content = versionById.get(a1!.currentVersionId!)?.content ?? '' for (const word of ['Autonomie', 'Équilibre', 'Liaison', 'toile de confiance Ğ1']) { expect(content).toContain(word) } }) it('le Pacte résout les réglages v2 (resolveSettings) avec les seuils du seed', () => { const pact = docBySlug('pacte-duniter-g1') const settings = resolveSettings(clausesOf(pact), bundle.versions) expect(settings.triage).toEqual({ smallGroupMax: 5, collectiveMin: 50, consentMax: 7, objectionWindowHours: 48, adviceWindowHours: 72, framingDays: 14, concernEscalateRatio: 0.5, recurrenceThreshold: 3, reviewDelayDays: 90, requireEffects: 'binding', }) // consent OBLIGATOIRE + large = binaire inertiel (héritage free-currency) expect(protocolById.get(settings.protocolByRange.consent)?.method).toBe('consent') expect(protocolById.get(settings.protocolByRange.large!)?.name).toBe('Vote WoT standard') expect(protocolById.get(settings.protocolByRange.nuanced!)?.method).toBe('nuanced') expect(protocolById.get(settings.protocolByRange.parametric!)?.method).toBe('parametric') expect(protocolById.get(settings.protocolByRange.election!)?.method).toBe('election') }) it('clauseByInertia : les 4 presets pointent les formules v1 (M/G intacts)', () => { const pact = docBySlug('pacte-duniter-g1') const settings = resolveSettings(clausesOf(pact), bundle.versions) const byInertia = settings.protocolByRange.clauseByInertia expect(byInertia).toBeDefined() const expected: Record = { low: { M: 50, G: 0.1 }, standard: { M: 50, G: 0.2 }, high: { M: 60, G: 0.4 }, max: { M: 66, G: 0.6 }, } for (const preset of Object.keys(expected) as InertiaPreset[]) { const protocol = protocolById.get(byInertia![preset]) expect(protocol, preset).toBeDefined() expect(protocol!.formula.majorityPct, preset).toBe(expected[preset].M) expect(protocol!.formula.gradientExponent, preset).toBe(expected[preset].G) expect(protocol!.formula.baseExponent, preset).toBe(0.1) } // La clause qui régit l'inertie est elle-même en preset 'max' (méta-protection) const metaClauses = clausesOf(pact).filter((c) => c.settingKey?.startsWith('protocols.clauseByInertia.'), ) expect(metaClauses.length).toBe(4) for (const clause of metaClauses) expect(clause.inertia, clause.code).toBe('max') }) }) describe('non-perte — personnes, protocoles, mandats', () => { it('11 personnes dont exactement 5 smiths', () => { expect(bundle.people).toHaveLength(11) expect(bundle.people.filter((p) => p.wotStatus === 'smith')).toHaveLength(5) for (const name of ['Moul', 'Poka', 'Hugo', 'Elois', 'Cgeek']) { expect(personByName.get(name)?.wotStatus, name).toBe('smith') } }) it('≥ 7 protocoles, tous ballot open, avec le Consentement OBLIGATOIRE', () => { expect(bundle.protocols.length).toBeGreaterThanOrEqual(7) for (const p of bundle.protocols) expect(p.ballot, p.name).toBe('open') expect(bundle.protocols.filter((p) => p.method === 'consent')).toHaveLength(1) const methods = new Set(bundle.protocols.map((p) => p.method)) for (const m of ['consent', 'binary', 'nuanced', 'parametric', 'election']) { expect(methods.has(m as Protocol['method']), m).toBe(true) } }) it('formules v1 intactes : WoT standard, Smith S.1, ComTech T.1, nuancé 59/80', () => { const wot = bundle.protocols.find((p) => p.name === 'Vote WoT standard')! expect(wot.modeParams).toBe('D30M50B.1G.2') expect(wot.formula).toMatchObject({ majorityPct: 50, baseExponent: 0.1, gradientExponent: 0.2 }) expect(wot.description).toContain('dernier recours des très grands corps') expect(wot.description).toContain('héritage Toile de Confiance') const smith = bundle.protocols.find((p) => p.name === 'Vote forgeron (Smith)')! expect(smith.modeParams).toBe('D30M50B.1G.2S.1') expect(smith.formula.smithExponent).toBe(0.1) const tech = bundle.protocols.find((p) => p.name === 'Vote Comité Technique')! expect(tech.modeParams).toBe('D30M50B.1G.2T.1') expect(tech.formula.techcommExponent).toBe(0.1) const nuanced = bundle.protocols.find((p) => p.method === 'nuanced')! expect(nuanced.durationDays).toBe(30) expect(nuanced.formula.nuancedMinParticipants).toBe(59) expect(nuanced.formula.nuancedThresholdPct).toBe(80) const parametric = bundle.protocols.find((p) => p.method === 'parametric')! expect(parametric.durationDays).toBe(14) expect(parametric.formula.parametricMinParticipants).toBe(5) const election = bundle.protocols.find((p) => p.method === 'election')! expect(election.durationDays).toBe(14) expect(election.formula.electionMinParticipants).toBe(5) expect(election.formula.tieBreak).toBe('runoff') }) it('3 mandats : ComTech actif (Cgeek, 1 rapport dû) · Admin Forgerons proposé · Modération active', () => { expect(bundle.mandates).toHaveLength(3) const comtech = bundle.mandates.find((m) => m.title.includes('Comité Technique')) as Mandate expect(comtech.status).toBe('active') expect(comtech.holderId).toBe(personByName.get('Cgeek')!.id) const due = comtech.reports.filter((r) => !r.deliveredAt) expect(due.length).toBeGreaterThanOrEqual(1) const admin = bundle.mandates.find((m) => m.title.includes('Administrateur')) as Mandate expect(admin.status).toBe('proposed') const forum = bundle.mandates.find((m) => m.title.includes('Modération')) as Mandate expect(forum.status).toBe('active') expect(forum.domain.tags).toEqual(['forum', 'moderation']) }) it('le mandat proposé est EN vote nuancé : session ouverte + 2-3 votes', () => { const admin = bundle.mandates.find((m) => m.title.includes('Administrateur')) as Mandate const origin = decisionById.get(admin.originDecisionId)! expect(origin.status).toBe('voting') expect(origin.createsMandate).toBeDefined() expect(protocolById.get(origin.protocolId!)?.method).toBe('nuanced') const session = bundle.sessions.find((s) => s.decisionId === origin.id) as VoteSession expect(session).toBeDefined() expect(session.status).toBe('open') expect(session.corpusSize).toBe(session.corpusPersonIds.length) const sessionVotes = bundle.votes.filter((v) => v.sessionId === session.id) as Vote[] expect(sessionVotes.length).toBeGreaterThanOrEqual(2) expect(sessionVotes.length).toBeLessThanOrEqual(3) for (const vote of sessionVotes) { expect(typeof vote.value).toBe('number') expect(bundle.people.some((p) => p.id === vote.voterId)).toBe(true) } }) }) describe('invariant — la décision est le pivot', () => { it('chaque clause courante a sa décision fondatrice adoptée, chaînée', () => { for (const clause of bundle.clauses) { expect(clause.currentVersionId, `currentVersionId de ${clause.code}`).toBeTruthy() const version = versionById.get(clause.currentVersionId!)! expect(version.status, `version de ${clause.code}`).toBe('current') const decision = decisionById.get(version.decisionId)! expect(decision, `décision fondatrice de ${clause.code}`).toBeDefined() expect(decision.status).toBe('adopted') expect(decision.route).toBe('collective') expect(decision.title).toBe(`Adoption fondatrice — ${clause.code}`) expect(decision.amendsClauseId).toBe(clause.id) expect(decision.decidedAt).toBeTruthy() } }) it('décisions historiques : Runtime Upgrade 1100 gravée + licence v0.4.0 en formulation', () => { const runtime = bundle.decisions.find((d) => d.title === 'Runtime Upgrade 1100')! expect(runtime.status).toBe('adopted') expect(runtime.weight).toBe('structural') expect(runtime.engraving?.proofLevel).toBe('local') expect(runtime.engraving?.sha256).toMatch(/^[0-9a-f]{64}$/) expect(runtime.review?.dueAt).toBeTruthy() expect(runtime.brief?.effects.length).toBeGreaterThanOrEqual(1) expect(runtime.brief?.effects.some((e) => e.target && e.measured)).toBe(true) const licence = bundle.decisions.find((d) => d.title === 'Évolution Licence G1 v0.4.0')! expect(licence.status).toBe('framing') const proposed = bundle.versions.filter( (v) => v.decisionId === licence.id && v.status === 'proposed', ) expect(proposed.length).toBeGreaterThanOrEqual(1) const certDoc = docBySlug('engagement-certification') const certClauseIds = new Set(clausesOf(certDoc).map((c) => c.id)) for (const version of proposed) { expect(certClauseIds.has(version.clauseId), 'version proposée liée à une clause cert').toBe(true) } }) })