feat: media-send pacing na élke send (niet alleen tussen pagina's)

MEDIA_SEND_DELAY_MS pauzeert nu centraal in sendImageMessage ná elke
media-send, zodat ook afbeeldingen naar verschillende ontvangers niet
overlappen in de ene chromium-tab (staged-ghost race-risico). Per-pagina
delay uit messageHandler verwijderd.
This commit is contained in:
Eddy de Vink 2026-09-26 22:26:18 +02:00
commit 7673239933
5 changed files with 83 additions and 20 deletions

View file

@ -10,13 +10,18 @@ zag in de chat maar **één** bericht (ghost-sends: id geretourneerd, nooit echt
- **`/chat/fetchMessages`-acks en de lokale message-store van wwebjs-api zijn een lokale
registratie, géén bezorgbewijs.** Ze kunnen ack 1/2/3 tonen voor berichten die nooit
aankwamen.
- **Enige geldige bewijzen van levering:** (1) de **live `message_ack`-progressie** zoals
die vlak ná verzending via de webhook binnenkomt; (2) de **zichtbare werkelijkheid op
het apparaat** van de ontvanger/betrokkene.
- **Ook de live `message_ack`-progressie is sinds 2026-09-26 géén bezorgbewijs meer:**
de staged-ghost van dienst 5219 blz 2 kreeg via de webhook een `ack=2` afgegeven voor een
bericht dat nooit getransmitteerd was (stond niet eens in de WhatsApp-app van de eigenaar).
De bridge resolvet media-sends **optimistisch op lokale staging** en fabriceert daarna
ack-events + delivery-info. Ack-tracking (stopt bij ack ≥ 2) kan deze klasse per ontwerp
niet vangen.
- **Enige geldige bewijzen van levering:** (1) de **zichtbare werkelijkheid op het
apparaat** van de ontvanger/betrokkene; (2) `getInfo` (`message.getInfo()`): bij een
staged-ghost blijft `read` leeg terwijl `delivery` gevuld kan zijn — het enige
betrouwbare bridge-signaal is `read`/`readRemaining`.
- **Trek NOOIT een leveringsconclusie uit een later gelezen fetchMessages-ack.** Claim geen
"bezorgd/gelezen" op basis van de store — dat geeft verkeerde adviezen.
- Dit is de reden dat ack-tracking bestaat: het hangt af van de live events, niet van de
store.
## Waar draait de bot?
@ -176,13 +181,47 @@ Sommige mediabestand-verzendingen keren `success` + message-ID terug maar worden
- **Pruning** na `ACK_PRUNED_AFTER_MS` (30 min); de check-loop is **activity-driven** (draait alleen bij openstaande tracking, geen polling in rust).
- Vereiste aan de bridge: `message_ack` staat sinds 2026-09-24 in wwebjs-api `.env` **niet** meer in `DISABLED_CALLBACKS`; container is daarvoor herstart.
- Env-vars: `ACK_STUCK_TIMEOUT_MS` (default 300000), `ACK_PRUNED_AFTER_MS` (default 1800000), `ACK_CHECK_INTERVAL_MS` (default 30000).
- **Media-spreiding:** `MEDIA_SEND_DELAY_MS` (default **500**) pauzeert tussen de pagina's van één
dienst (niet vóór de eerste). Doel: de wwebjs media-pipeline (één chromium-tab) per-verzending
lucht geven en gelijktijdige storage/upload-conflicten verminderen. 0 = uitschakelen.
- **Media-spreiding:** `MEDIA_SEND_DELAY_MS` (default **500**, nu **1500**) pauzeert **na élke
media-send** (in `sendImageMessage`), niet alleen tussen pagina's van één dienst. De
wwebjs-media-pipeline is EEN chromium-tab; opeenvolgende media-sends — óók naar
verschillende ontvangers — overlappen anders en veroorzaken de staged-ghost-race.
0 = uitschakelen. (Tijdelijke mitigatie; permanente fix = fork: send óf écht verzenden
óf zichtbaar falen, zie RCA.)
Logregels: `[ack] track <id> (<bestand> → <chat>)`, `[ack] delivered ack=<n> <id>`, `[ack] pruned <n> stale entries`.
**Ack-semantiek (wwebjs 1.34.7):** ack 1 = verstuurd naar server (één grijs vinkje), ack 2 = bezorgd op apparaat (twee vinkjes), ack 3 = geopend/gelezen. Let op: eerdere notities die "één grijs vinkje (ack 2)" noemden, zaten er dus naast.
**Ack-semantiek (wwebjs 1.34.7):** ack 1 = verstuurd naar server (één grijs vinkje), ack 2 = bezorgd op apparaat (twee vinkjes), ack 3 = geopend/gelezen. Let op: eerdere notities die "één grijs vinkje (ack 2)" noemden, zaten er dus naast. **Let op 2 (2026-09-26):** óók deze acks zijn geen leveringsbewijs — zie RCA hieronder.
### RCA 2026-09-26: staged-ghost = race-conditie, géén bestandprobleem
Casus: dienst 5219 (2 pagina's), 09:29 gevraagd in de groep, bot stuurt beide pagina's
prive. App toont alleen blz 1; **blz 2 stond in het geheel niet in de WhatsApp-app van de
eigenaar** (geen transmissie). Toch: bridge retourneerde success + message-ID (`3EB040AD…`),
store toont het bericht, live `message_ack` gaf `ack=2`, `getInfo` toonde `delivery` gevuld
maar `read` leeg. Geen enkele fout in de container-logs.
Mechanisme (bewezen uit fork-broncode + logs):
- De send loopt via `WAWebSendMsgChatAction.addAndSendMsgToChat(chat, message)` →
`[msgPromise, sendMsgResultPromise]`. De bridge zet `waitUntilMsgSent: true`
(stock wwebjs-api, `clientController.js`), maar de promise resolve **ook optimistisch op
lokale staging** — een echte serverbevestiging wordt nooit afgewacht. HTTP-success +
message-ID betekenen dus **niet** "verzonden".
- **Patroon (gebruiker, consistent met logs):** de ghost treedt alleen op bij **2+ media
direct achter elkaar**, en dan altijd de **2e** van het paar; enkele sends falen nooit.
- Verklaring: de bot wacht per pagina op het HTTP-signaal (staging), niet op echte
transmissie → blz 2 wordt gestart terwijl blz 1's **echte** upload/transmissie nog in
de lucht is. Twee echte media-transmissies overlappen in de ene chromium-tab → de 2e
verliest (intermitterend) → staged-ghost.
- Gevolgen: **`MEDIA_SEND_DELAY_MS` kan dit niet oplossen** (wacht op optimistisch
signaal); **poppler/re-render maskeert het alleen** (nieuwe poging = nieuwe race-rol,
soms gewonnen — wassen neus); **`sendMessageViaUi` lost het óók niet op** (wacht op
dezelfde promises, alleen omzeilt het de media-editor); **ack-tracking kan het niet
vangen** (stopt bij gefabriceerde ack ≥ 2).
Enige betrouwbare signalen: apparaat-waarheid; `getInfo` `read`/`readRemaining` (blijft
leeg bij staged). Aanbevolen richting (nog niet geïmplementeerd): de fork moet ná
`addAndSendMsgToChat` écht wachten op transmissiebevestiging — pas dan serialiseren
multi-page sends vanzelf en is de race weg.
## Media bezorgtest (afbeeldingen die WhatsApp weigert)
@ -200,6 +239,15 @@ ack-tracking (zie hierboven), re-render via poppler en een verse bezorgtest.
**Ack-telling:** ack 1 = één grijs vinkje (niet bezorgd), ack 2 = bezorgd op apparaat,
ack 3 = geopend. (Eerdere notities "één grijs vinkje (ack 2)" zaten er dus naast.)
> **⚠️ 2026-09-26: onderscheid twee foutklassen.** De hierboven beschreven
> bestand × ontvanger/offline-klasse (ack 1-hang, ontvanger offline of app weigert
> render) is niet hetzelfde als de **staged-ghost-race** (zie RCA hierboven): daar is het
> bestand nooit het probleem en **re-render (poppler) is een wassen neus** — het maskert
> de race alleen. Check bij een "niet aangekomen bladzijde" eerst: was het de 2e pagina
> van een paar binnen ~1s? Dan is het de race, geen bestand. Echte remedie is nog in
> ontwerp (fork: wachten op transmissiebevestiging); voor nu: opnieuw versturen (eventuele
> nieuwe poging) en apparaat als waarheid.
**Na elke omruiling van de dienstenmap: de volledige media-test doorlopen vóór ingebruikname.**
**Afbeeldingsformaat:** sinds `07b511e` leidt `sendImageMessage` de mimetype af uit de