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,127 @@
/**
* Ported from backend/app/tests/test_mode_params.py — every pytest case
* exists here with the same expected values, plus tests for
* formatModeParams() (no Python counterpart: defined as the canonical
* inverse of parseModeParams, matching the seed DSL strings).
*/
import { describe, expect, it } from 'vitest'
import { formatModeParams, parseModeParams } from '../../app/engine/modeParams'
describe('parseModeParams', () => {
it('D30M50B.1G.2 => standard Licence G1 params', () => {
const result = parseModeParams('D30M50B.1G.2')
expect(result.duration_days).toBe(30)
expect(result.majority_pct).toBe(50)
expect(result.base_exponent).toBe(0.1)
expect(result.gradient_exponent).toBe(0.2)
// Optional criteria absent
expect(result.smith_exponent).toBeNull()
expect(result.techcomm_exponent).toBeNull()
})
it('D30M50B.1G.2S.1 => standard + smith_exponent=0.1', () => {
const result = parseModeParams('D30M50B.1G.2S.1')
expect(result.duration_days).toBe(30)
expect(result.majority_pct).toBe(50)
expect(result.base_exponent).toBe(0.1)
expect(result.gradient_exponent).toBe(0.2)
expect(result.smith_exponent).toBe(0.1)
expect(result.techcomm_exponent).toBeNull()
})
it('D30M50B.1G.2T.1 => standard + techcomm_exponent=0.1', () => {
const result = parseModeParams('D30M50B.1G.2T.1')
expect(result.duration_days).toBe(30)
expect(result.majority_pct).toBe(50)
expect(result.base_exponent).toBe(0.1)
expect(result.gradient_exponent).toBe(0.2)
expect(result.smith_exponent).toBeNull()
expect(result.techcomm_exponent).toBe(0.1)
})
it('D30M50B1G.5C10 => integer base, gradient=0.5, constant=10', () => {
const result = parseModeParams('D30M50B1G.5C10')
expect(result.duration_days).toBe(30)
expect(result.majority_pct).toBe(50)
expect(result.base_exponent).toBe(1.0)
expect(result.gradient_exponent).toBe(0.5)
expect(result.constant_base).toBe(10.0)
})
it('empty string returns all defaults', () => {
const result = parseModeParams('')
expect(result.duration_days).toBe(30)
expect(result.majority_pct).toBe(50)
expect(result.base_exponent).toBe(0.1)
expect(result.gradient_exponent).toBe(0.2)
expect(result.constant_base).toBe(0.0)
expect(result.smith_exponent).toBeNull()
expect(result.techcomm_exponent).toBeNull()
expect(result.ratio_multiplier).toBeNull()
expect(result.is_ratio_mode).toBe(false)
})
it('whitespace-only string treated as empty', () => {
const result = parseModeParams(' ')
expect(result.duration_days).toBe(30)
})
it('result exposes exactly the expected keys', () => {
const result = parseModeParams('D30M50B.1G.2')
const expectedKeys = [
'duration_days',
'majority_pct',
'base_exponent',
'gradient_exponent',
'constant_base',
'smith_exponent',
'techcomm_exponent',
'ratio_multiplier',
'is_ratio_mode',
].sort()
expect(Object.keys(result).sort()).toEqual(expectedKeys)
})
it('unknown code letter throws (Python parity)', () => {
expect(() => parseModeParams('D30X5')).toThrow(/inconnu/)
})
it('D7R1N2.5 => ratio mode with multiplier (Python cross-check)', () => {
const result = parseModeParams('D7R1N2.5')
expect(result.duration_days).toBe(7)
expect(result.is_ratio_mode).toBe(true)
expect(result.ratio_multiplier).toBe(2.5)
// Untouched defaults
expect(result.majority_pct).toBe(50)
expect(result.base_exponent).toBe(0.1)
expect(result.gradient_exponent).toBe(0.2)
})
})
describe('formatModeParams', () => {
it('defaults format to the canonical standard string', () => {
expect(formatModeParams()).toBe('D30M50B.1G.2')
expect(formatModeParams({})).toBe('D30M50B.1G.2')
})
it('round-trips the seed DSL strings', () => {
for (const s of ['D30M50B.1G.2', 'D30M50B.1G.2S.1', 'D30M50B.1G.2T.1']) {
expect(formatModeParams(parseModeParams(s))).toBe(s)
}
})
it('formats integer base and constant', () => {
expect(formatModeParams(parseModeParams('D30M50B1G.5C10'))).toBe('D30M50B1G.5C10')
})
it('formats ratio mode and multiplier', () => {
expect(formatModeParams(parseModeParams('D7R1N2.5'))).toBe('D7M50B.1G.2N2.5R1')
})
it('parse(format(p)) is identical to p', () => {
for (const s of ['D30M50B.1G.2S.1', 'D30M50B1G.5C10', 'D7R1N2.5', 'D90M66B.2G.3S.2T.1']) {
const p = parseModeParams(s)
expect(parseModeParams(formatModeParams(p))).toEqual(p)
}
})
})