v3 : serveur des objections, des coquilles validées et de la messagerie (lot F)

Objections « redit » / « contredit » à la place des signalements : la
dernière arrivée des deux versions n'est plus votable (repli, panier,
chaîne : un vote déjà scellé est gardé mais ne la fait pas entrer) ; un
texte en vigueur ne l'est jamais. L'auteur répond en modifiant l'article
visé (proposition liée) ou en reformulant ; la personne qui objecte lève
ou réitère depuis ses messages ; sans réponse sous 3 jours, l'objection
est levée. Un administrateur peut lever.

Coquilles : la sienne passe tout de suite ; celle d'un autre attend
l'auteur (valider / refuser), passe seule au bout de 3 jours, et l'auteur
garde la main tant que le texte n'est pas en vigueur. En vigueur, seule
une coquille validée corrige (auteur de la version, administrateur pour
un texte semé). Deux coquilles en attente : la seconde se mesure au texte
voté ; à l'application, celle qui ne colle plus est périmée.

Messagerie dans l'application (boîte, non-lus, gestes, échéance), chaque
personne ne lit que les siens. Échéances par le balayage existant, avec
rattrapage au démarrage et horloge injectable (route de dev). Migration
0008 additive : les signalements deviennent des « contredit », sans perte.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Yvv
2026-10-31 02:45:48 +01:00
co-authored by Claude Opus 5.5
parent 70dbd712c8
commit 3f1f968f16
17 changed files with 2754 additions and 238 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);