Fusion de la tête du chantier (924a40d : lots B2 et F) dans le lot D

Les deux côtés gardés partout. Aux coutures :
- une objection (redit / contredit) porte sur deux articles, jamais sur un module ;
  un ordre ne se corrige pas, ni par son auteur ni par un autre ; une coquille du
  titre suit le chemin de F (l'auteur du titre, un administrateur pour le titre
  de départ, ou 3 jours), et documents.title suit à l'application ;
- votable() couvre les modules ; leurs totaux passent par shown_tally, à l'écran
  et dans document.json ;
- « Modules à voter » ne compte plus une version retenue par une objection (test
  vu rouge) ; la liste du groupe la compte toujours parmi les propositions en cours ;
- le panier : une fonction pour les articles et les modules, avec les versions
  retenues de F ; un nouvel article se propose dans une section, à sa fin, et peut
  répondre à une objection ;
- migrations 0007 (modules) puis 0008 (objections) ;
- C25 et C26 supportent un groupe d'essai où une voix suffit (dès 16 membres).

Boucle : fmt, clippy -D warnings, cargo test (227), svelte-check 0, vitest 55,
build, e2e 35 / 35 (C1–C33 et M1).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Yvv
2026-10-31 09:41:30 +01:00
co-authored by Claude Opus 5.5
82 changed files with 8285 additions and 677 deletions
@@ -0,0 +1,93 @@
-- Objections « redit » / « contredit », typo fixes validated, and the messages
-- of trustinWallet (2026-10-31, lot F, ADR 0010). Additive only: nothing is
-- dropped, nothing is rewritten that an older image needs.
--
-- The contradiction signals of before become objections « contredit », in the
-- same table: an older image (rollback) still reads them as signals, and every
-- lifting still writes `lifted_at` / `lifted_by` as before.
ALTER TABLE signals ADD COLUMN kind TEXT NOT NULL DEFAULT 'contredit'; -- redit | contredit
ALTER TABLE signals ADD COLUMN held_id TEXT; -- the version held back (not votable): the latest arrival, never one in force
ALTER TABLE signals ADD COLUMN phase TEXT NOT NULL DEFAULT 'author'; -- author | objector | lifted
ALTER TABLE signals ADD COLUMN cycle INTEGER NOT NULL DEFAULT 1; -- a reiteration opens the next
ALTER TABLE signals ADD COLUMN due_at TEXT; -- phase objector: lifted by itself at this time
ALTER TABLE signals ADD COLUMN answered_at TEXT; -- the last answer of the author
ALTER TABLE signals ADD COLUMN answer TEXT; -- revision | other | proposal
ALTER TABLE signals ADD COLUMN answer_id TEXT; -- the version revised, or the proposal linked
ALTER TABLE signals ADD COLUMN lift TEXT; -- objector | admin | deadline | withdrawn | revision
-- The signals of before that were lifted stay lifted: by their author, by an
-- administrator, or because the text changed (the rule of before).
UPDATE signals SET phase = 'lifted', lift = CASE
WHEN lifted_by = 'revision' THEN 'revision'
WHEN lifted_by = author_id THEN 'objector'
ELSE 'admin' END
WHERE lifted_at IS NOT NULL;
-- The standing ones wait for the author of the version they now hold back: the
-- latest arrival of the two (submitted last; the same instant: written last),
-- unless it is in force — then the other. Both in force: none is held.
UPDATE signals SET held_id = (
SELECT CASE
WHEN a.status = 'in_force' AND b.status = 'in_force' THEN NULL
WHEN (IFNULL(a.submitted_at, a.created_at), a.rowid, a.id) > (IFNULL(b.submitted_at, b.created_at), b.rowid, b.id)
THEN CASE WHEN a.status = 'in_force' THEN b.id ELSE a.id END
ELSE CASE WHEN b.status = 'in_force' THEN a.id ELSE b.id END
END
FROM proposals a, proposals b
WHERE a.id = signals.proposal_id AND b.id = signals.target_proposal_id)
WHERE lifted_at IS NULL;
CREATE INDEX signals_proposal ON signals(proposal_id);
CREATE INDEX signals_target ON signals(target_proposal_id);
CREATE INDEX signals_due ON signals(phase, due_at);
-- A proposal written to answer an objection (« modifier l'article visé »).
ALTER TABLE proposals ADD COLUMN objection_id TEXT;
-- A typo fix written by someone other than the one who answers for the version:
-- it waits for them (or for the deadline), then applies — or not.
CREATE TABLE typos (
id TEXT PRIMARY KEY,
proposal_id TEXT NOT NULL REFERENCES proposals(id),
doc_id TEXT NOT NULL REFERENCES documents(id),
writer_id TEXT NOT NULL REFERENCES identities(id), -- who wrote the fix
answerer_id TEXT, -- the author of the version, who answers; NULL = administrators (no author)
revision INTEGER NOT NULL, -- the revision it was written over
base_title TEXT NOT NULL, -- the text it was written over
base_body TEXT NOT NULL,
title TEXT NOT NULL, -- the fix
body TEXT NOT NULL,
status TEXT NOT NULL, -- pending | applied | refused | stale
created_at TEXT NOT NULL,
due_at TEXT NOT NULL, -- applied by itself then, if nobody answered
settled_at TEXT,
settled_by TEXT -- identity id, 'deadline', or 'changed' (stale)
);
CREATE INDEX typos_proposal ON typos(proposal_id, status);
CREATE INDEX typos_due ON typos(status, due_at);
-- The revision a validated fix wrote, or the one that took it back (kind 'revert'),
-- and who let it in: the identity that validated it, or 'deadline'.
ALTER TABLE revisions ADD COLUMN typo_id TEXT;
ALTER TABLE revisions ADD COLUMN answered_by TEXT;
-- The messages of trustinWallet: one person, one box. The text is told by the
-- interface from the subject (in the language of the reader); the gestures
-- follow the subject's state, so a message dealt with offers none.
CREATE TABLE messages (
id TEXT PRIMARY KEY,
recipient_id TEXT NOT NULL REFERENCES identities(id),
doc_id TEXT NOT NULL REFERENCES documents(id),
topic TEXT NOT NULL, -- objection | typo
subject_id TEXT NOT NULL, -- signals.id | typos.id
kind TEXT NOT NULL, -- objection_raised | objection_reiterated | objection_answered | typo_pending
cycle INTEGER NOT NULL DEFAULT 1,
created_at TEXT NOT NULL,
due_at TEXT,
read_at TEXT,
done_at TEXT,
outcome TEXT -- answered | lifted | reiterated | deadline | admin | withdrawn | applied | refused | stale
);
CREATE INDEX messages_box ON messages(recipient_id, created_at);
CREATE INDEX messages_subject ON messages(topic, subject_id);