L'envoi suspendu

Mardi 19 mai, dix heures vingt. Je m'apprête à lancer la deuxième vague de SMS de réinscription. Cinquante-trois contacts anciens d'un atelier de céramique, un lien court de la forme /r/<short_code> qui renvoie chacun sur son tunnel personnel. Pré-flight standard : je grep les tokens correspondants pour vérifier qu'ils sont actifs. Cinquante-deux retournent ce que j'attends. Le cinquante-troisième porte un used_at non null, daté du matin même, parce que le tunnel de test que j'avais passé deux heures plus tôt avait consommé le token. Si je lance l'envoi, ce contact reçoit un lien qui répondra 410 Gone. Catherine, qui teste la version mobile depuis l'antenne, m'écrit au même moment qu'elle voudrait que je vérifie « les cinquante-trois quand même, parce que tu sais comment c'est ». Elle a raison. Je n'envoie pas.

La règle qui m'avait servi pendant deux mois

Depuis la fin avril, je vis sous une règle simple, énoncée en quatre lignes dans la doctrine du projet et que je tiens pour le filet le plus efficace que j'aie posé jusque-là. Toute valeur stockée qui ressemble à une duplication doit être classée Live, Snapshot ou Cache avant d'être créée, et chaque catégorie impose une implémentation différente. Live, c'est ne pas stocker — on lit à la volée via une vue. Snapshot, c'est stocker et ne jamais recalculer — l'historique est intouchable. Cache, c'est stocker mais déclarer dans le même commit le mécanisme de rafraîchissement — trigger nommé, GENERATED ALWAYS AS, ou vue matérialisée. Aucun cache ne survit sans son rafraîchisseur explicite ; si on ne peut pas le garantir, on retombe sur Live.