forked from yvv/decision
Lot V : contrôle C34 (la chaîne fermée, Monnaie libre vote quand même : le vote simulé compte aussitôt, marqué dans « Tes bulletins ») ; C24 dit que le serveur sans compte des votes simule depuis le lot V ; CLAUDE.md et CONTROLES.md (buffer, casts_simulated, simulation.rs, C34, 35 contrôles / 36 tests)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -40,10 +40,13 @@ crates/
|
||||
# fait foi, jugement d'une transaction), address (SS58, préfixe g1), objection
|
||||
# (redit / contredit : la dernière arrivée retenue, les tours, l'échéance), correction
|
||||
# (la coquille d'un autre : attendre, valider, refuser, l'échéance), order (l'ordre
|
||||
# d'une section : qui en est, complété à la fin, places), pending (modules à voter)
|
||||
# d'une section : qui en est, complété à la fin, places), pending (modules à voter),
|
||||
# buffer (le tampon des votes, ADR 0012 : réconciliation, poids des états,
|
||||
# SIMULATED_ADOPTS)
|
||||
trustin-access/ # règles d'accès de trustinWallet : sees / opens / manages_access, vote_way (serveur ou
|
||||
# chaîne), answers_typo, lifts_objection / reiterates_objection, reads_message,
|
||||
# sets_quorum, groupes par défaut (TEST_GROUP)
|
||||
# sets_quorum, casts_simulated (le vote simulé, chaîne fermée), groupes par défaut
|
||||
# (TEST_GROUP)
|
||||
ld-server/
|
||||
migrations/ # schéma SQLite — additif seulement
|
||||
src/ # lib.rs (routes, csrf, run) · config.rs (LD_*) · auth.rs (connect OAuth2, invitations,
|
||||
@@ -55,7 +58,9 @@ crates/
|
||||
# jour) · publish.rs (file → git) · wot.rs (W sur la chaîne) · seed.rs ·
|
||||
# seed_amend.rs / inertia_amend.rs / seed_proposal_amend.rs (amendements gardés ;
|
||||
# le dernier fait des Propositions seed, ADR 0011) · ballots.rs (vote groupé,
|
||||
# bulletins de la chaîne Ğ1, confirmation, « ton vote ») · indexer.rs (txComments)
|
||||
# bulletins de la chaîne Ğ1, le tampon : intention, preuve, réconciliation ; le vote
|
||||
# simulé, « ton vote ») · simulation.rs (l'acte « supprime » de la fin de la simulation :
|
||||
# `ld-server discard-simulation [--apply]`, ADR 0012) · indexer.rs (txComments)
|
||||
# · modules.rs / order.rs / doc_title.rs (les modules, ADR 0009 : l'ordre d'une
|
||||
# section, le titre du document) · module_amend.rs (ajout gardé) · quorum.rs (le quorum
|
||||
# d'un article, PUT /units/{id}/quorum ; « un vote existe » : voted_units, ADR 0013) ·
|
||||
@@ -87,7 +92,7 @@ web/src/
|
||||
# · g/[group]/decisions · g/[group]/mandats ·
|
||||
# g/[group]/protocoles (à venir) · acces (trustinWallet) · messages (la boîte) ;
|
||||
# /groupes, /decisions… redirigent
|
||||
web/e2e/ # Playwright C1–C33 + M1 ; serve.sh = serveur jetable /tmp/ld-e2e sur :8092 et
|
||||
web/e2e/ # Playwright C1–C34 + M1 ; serve.sh = serveur jetable /tmp/ld-e2e sur :8092 et
|
||||
# chain-mock.mjs (nœud et indexeur Ğ1 simulés) ; faux portefeuille dans helpers.ts
|
||||
docker/ # app.Dockerfile · app-compose.yml (sonic)
|
||||
.woodpecker/ # app.yml · maquette.yml
|
||||
@@ -109,7 +114,7 @@ maquette/ # v2 intacte (frontend Nuxt, backend FastAPI, docker, docs-v
|
||||
```bash
|
||||
export PATH=$HOME/.cargo/bin:$PATH
|
||||
cargo fmt --all --check && cargo clippy --workspace --all-targets -- -D warnings && cargo test --workspace
|
||||
cd web && npm run check && npm test && npm run build && npm run e2e # e2e : 34 contrôles (35 tests, C25 en deux)
|
||||
cd web && npm run check && npm test && npm run build && npm run e2e # e2e : 35 contrôles (36 tests, C25 en deux)
|
||||
|
||||
LD_DEV_LOGIN=1 cargo run -p ld-server # :8002, sert web/build
|
||||
cd web && npm run dev # :3002 à chaud
|
||||
|
||||
@@ -49,6 +49,7 @@ pour une personne, ils coûtent 0,1 Ğ1 réels : c'est un acte « paye » d'Yvv.
|
||||
| C31 | Coquille sur un texte en vigueur | groupe d'essai : une administratrice (Ada) ouvre le groupe ; Vera, sur « Raison d'être » → « Le vote » → « Coquilles, traductions, objections, historique » → « Corriger une coquille », « C'est le seul » → « C’est le seul » ; Ada ouvre « Messages » → « Valider » | chez Vera : « Ta correction attend un administrateur, ce texte venant du seed… » ; le texte en vigueur ne change pas (« C'est le seul ») ; la correction en attente se lit dans « Détails » ; chez Ada : « Vera propose une correction de coquille dans « Raison d'être », un texte du seed : un administrateur la valide. » ; après « Valider », le texte en vigueur porte « C’est le seul » et l'historique « validée par Ada » |
|
||||
| C32 | Lâcher ou garder un bulletin | Monnaie libre, un portefeuille dans le navigateur (Eve, chaîne simulée), « Propositions en cours » → Document de révocation : « Contre » le nœud éteint ; puis « Contre », fermer la fenêtre pendant que le portefeuille demande, refuser dans le portefeuille ; puis « Contre », fermer, signer dans le portefeuille ; une autre personne, sans portefeuille : « Contre » → « Passer par Ğecko », fermer, payer depuis un téléphone | nœud éteint : « La chaîne Ğ1 ne répond pas. Réessaie dans un moment. », le bulletin lâché aussitôt, rien « en attente » sous l'article ; fenêtre fermée puis refus : lâché ; fenêtre fermée puis signature : gardé, et la chaîne le confirme sans la page (« Ton vote : contre · inscrit sur la chaîne Ğ1 ») ; Ğecko, fenêtre fermée : « En attente de la chaîne Ğ1 : contre. », puis confirmé sans la page |
|
||||
| C33 | Le reçu de ton vote | Monnaie libre, un portefeuille : « Contre » sur Ne jamais certifier seul (« Propositions en cours »), « Télécharger ton reçu », « Tes bulletins » ; sur « Tes bulletins », « Fichier du reçu » : le reçu ; puis coller le reçu avec un choix changé, « Vérifier » ; une autre personne ouvre « Tes bulletins » | le reçu `recu-vote-<date>-<id>.json` : les votes, le sel, la remarque `ld1:…`, « confirmed », l'identité « Alice » ; « Tes bulletins de vote » : « Compté pour l'identité Ğ1 « Alice » » et la remarque ; « Le reçu tient : ses votes et son sel donnent bien ce commentaire. », « Ne jamais certifier seul : contre » ; changé : « Le reçu ne tient pas » ; l'autre personne : « Tu n'as encore envoyé aucun bulletin par la chaîne Ğ1. », et le reçu lui est refusé |
|
||||
| C34 | Le vote simulé, la chaîne fermée (ADR 0012) | Monnaie libre sur un serveur sans compte des votes (l'automate ferme la chaîne du serveur jetable, puis la rouvre) : « Propositions en cours » → Logiciel auditable → « Contre », sans portefeuille ; puis « Tes bulletins » | « Voter en lot » est là ; aucune fenêtre de portefeuille ; « Ton vote : contre » aussitôt, et un « contre » de plus dans les totaux ; dans l'API, `myVoteState` « simulated » et `tallySimulated` ; dans « Tes bulletins » : « Simulé : compté pour « Sacha » jusqu'à l'ouverture de la chaîne Ğ1 », sans bloc |
|
||||
|
||||
## Au-delà de la liste
|
||||
|
||||
|
||||
+50
-2
@@ -176,8 +176,9 @@ test('C23 — le vote par la chaîne Ğ1 : un geste, confirmé par l’indexeur
|
||||
|
||||
test('C24 — sans compte de vote, le vote par la chaîne est fermé et le dit ; le groupe d’essai vote', async ({ page }) => {
|
||||
await signIn(page, 'Chiara');
|
||||
// The e2e server has its vote account: the page is shown what a server without one answers
|
||||
// (the server itself: `without_a_vote_account_the_chain_vote_is_off_and_the_test_group_still_votes`).
|
||||
// The e2e server has its vote account: the page is shown a vote closed, as a server without
|
||||
// one answered before lot V. Since lot V (ADR 0012) a server without one votes in simulation
|
||||
// instead (the server: `without_a_vote_account_monnaie_libre_votes_in_simulation_…`; here, C34).
|
||||
await page.route(/\/api\/docs\/[^/?]+$/, async (route) => {
|
||||
const res = await route.fetch();
|
||||
const body = await res.json();
|
||||
@@ -213,3 +214,50 @@ test('C24 — sans compte de vote, le vote par la chaîne est fermé et le dit ;
|
||||
await unit.getByRole('button', { name: 'Le vote' }).click();
|
||||
await expect(unit.locator('section.fold').getByRole('button', { name: 'Pour', exact: true })).toBeVisible();
|
||||
});
|
||||
|
||||
/** The version of an article of Monnaie libre, by its title, as the API tells it to `page`. */
|
||||
async function version(page: import('@playwright/test').Page, title: string) {
|
||||
const g = await (await page.request.get('/api/groups/monnaie-libre')).json();
|
||||
const d = await (await page.request.get(`/api/docs/${g.docs[0].id}`)).json();
|
||||
type P = { title: string; tally: { against: number }; tallySimulated: { against: number }; myVoteState: string | null };
|
||||
const all: P[] = d.units.flatMap((u: { proposals: P[] }) => u.proposals);
|
||||
const p = all.find((x) => x.title === title);
|
||||
if (!p) throw new Error(`no version « ${title} »`);
|
||||
return { vote: d.vote as { way: string; open: boolean; simulated: boolean }, p };
|
||||
}
|
||||
|
||||
test('C34 — la chaîne fermée, Monnaie libre vote quand même : le vote simulé compte aussitôt, marqué « simulé » dans tes bulletins', async ({ page }) => {
|
||||
// Lot V (ADR 0012, Yvv 2026-11-02: « Il compte, puis s'efface »). The e2e server has its
|
||||
// chain: it is closed for this control only, then opened again. The vote goes to a
|
||||
// proposal no other control reads, and only « contre »: nothing enters into force.
|
||||
await signIn(page, 'Sacha');
|
||||
const chainOpen = async (open: boolean) =>
|
||||
(await (await page.request.post('/api/dev/vote-chain', { data: { open }, headers: { 'x-requested-with': 'libredecision' } })).json()).open;
|
||||
const title = 'Logiciel auditable';
|
||||
expect(await chainOpen(false)).toBe(false);
|
||||
try {
|
||||
const before = await version(page, title);
|
||||
expect(before.vote).toMatchObject({ way: 'server', open: true, simulated: true });
|
||||
await page.goto(DOC);
|
||||
// The basket is there, without a wallet: the vote leaves by the server.
|
||||
await expect(page.getByRole('button', { name: /^Voter en lot/ })).toBeVisible();
|
||||
await deepRow(page, title).click();
|
||||
const fold = page.getByRole('region', { name: `Le vote de « ${title} »` });
|
||||
await fold.getByRole('button', { name: 'Contre', exact: true }).click();
|
||||
await expect(page.getByRole('dialog', { name: 'Voter avec ton portefeuille Ğ1' })).toHaveCount(0);
|
||||
await expect(fold.locator('.voted')).toHaveText(/^Ton vote : contre/);
|
||||
await expect(fold.getByText(`0 pour · ${before.p.tally.against + 1} contre`)).toBeVisible();
|
||||
// Counted at once, marked as simulated.
|
||||
const after = await version(page, title);
|
||||
expect(after.p.myVoteState).toBe('simulated');
|
||||
expect(after.p.tallySimulated.against).toBe(before.p.tallySimulated.against + 1);
|
||||
// In her ballots: simulated, counted for her session, never a block of the chain.
|
||||
await page.goto('/bulletins');
|
||||
const ballot = page.locator('li.ballot').first();
|
||||
await expect(ballot.locator('.state')).toHaveText("Simulé : compté pour « Sacha » jusqu'à l'ouverture de la chaîne Ğ1");
|
||||
await expect(ballot.getByText('bloc', { exact: false })).toHaveCount(0);
|
||||
} finally {
|
||||
// The next controls need the chain.
|
||||
expect(await chainOpen(true)).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user