You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Tracking issue for the defects found in the post-cutover review (agents + manual verification, 2026-08-04). The legacy emote/card projection pollution was already repaired in production and is documented in docs/runbooks/adr003-cutover.md; everything below is a runtime fix deployable at normal cadence.
Ordered by priority:
1. Retry attempt budget (ADR §7's one unimplemented promise). Deliveries that fail deterministically (QQ risk-control retcode≠0, reactions on deleted targets) retry forever as failed/AttemptRetryable and block the per-endpoint ordered lane behind them — retryDelay caps the interval at 300s but nothing caps attempts (src/Max/Platform/Delivery.hs retry paths, claim ordering predicate in src/Max/Platform/Store.hs). Convert over-budget attempts to suppressed (content) or outcome_unknown (undecidable) so the lane releases.
2. LISTEN/NOTIFY listener can starve the pool into a deadlock.claimOrWait/waitForTimeline hold a listener connection and then request a second one for the recheck; with dispatch + delivery workers + a handful of admin timeline long-polls, all pool connections become listeners and every recheck blocks on acquiring a connection with no timeout (src/Max/DB/Notify.hs). Bound admin long-poll concurrency, or run the recheck on the pinned listener connection.
3. Blob read failure poisons the whole message.loadDeliveryMedia treats an unreadable blob the same as an over-limit one: error → DeliveryPermanentlyFailed, taking the message's text down with it (src/Max/Platform/Delivery.hs). A missing/unreadable blob is semantically sourceless — ADR §2 says it must fold to the text tier with a lower note; only genuine size-limit violations should stay permanent failures.
4. insertCanonical and verify disagree on the rendered_text source. Ingest renders from the unenriched 'Ingest body while maintenance verify/reproject recompute from the enriched canonical body + identity map, so verify false-positives (and dies) on post-cutover rows whose mention display got enriched. Render from the resolved body in insertCanonical (src/Max/Platform/Store.hs). Until fixed, do not run max-adr003-maintenance verify against the live database.
5. Re-stamp compartment source_hash. Migration 062 changed both the hash function and its input set (055 rewrote content, 059 added event_kind, 060 changed occurred_at), so every stored conversation_compartments.source_hash is stale: admin integrity checks report all historical compartments as bad_source_ranges and episode expansion flags spurious source mismatches. One offline UPDATE … SET source_hash = conversation_source_hash(…) re-stamp.
6. wechatpad deliveries never confirm. Self-message echoes are filtered before ingest (src/Max/Wechatpad.hs) so the content-match echo fallback never sees them, and listUnconfirmedDeliveries is only consumed by iMessage — every WeChat delivery parks at accepted_unconfirmed forever and the admin counter grows monotonically. The synthetic native id ("sent:" <> chat <> ":" <> take 32 body) also collides for repeated identical texts, which nativeEventAlreadyOwned then discards. Needs a real confirmation loop (or an honest terminal state) and a collision-free receipt id.
7. QQ multi-image resend regressed.[image#id] referencing a multi-image message emits all stored images (messageImageNodes, src/Max/ReplySend.hs) but QQ's maxNativeMedia = 1 folds every image after the first into bare [图片] text with no attachable URL for blob refs. Either raise QQ's media budget (it can send several images per message) or split lowered media across chunks.
Lower priority, recorded from the same review:
Permanent failures and deliberate suppressions share status='suppressed', so poisoned messages are indistinguishable from quiet no-ops in /api/platforms/status monitoring without grepping last_error.
Matrix/iMessage blob-backed inbound media never reaches the vision pipeline (enqueueCanonicalMedia only picks remote URLs), so models see bare [image] for non-QQ media even though the bytes are already in BlobStore.
ensureConfiguredEndpoint resets endpoint_mode/caps on every boot, so a hand-promoted mirror reverts to standalone; no supported configuration lets a wechatpad room join a mirror conversation despite the code path existing.
Non-QQ conversations advertise the [@#<QQ号>] mention syntax and accept hallucinated numeric ids when no roster exists, minting fake principal identities on the destination account (src/Max/ReplySend.hs known-check, prompt advertisement in src/Max/Prompt/System.hs).
max_native_media > 1 on Matrix/iMessage endpoints permanently fails any multi-media chunk (ContractFailure): chunking never splits media, so the declared budget is unusable above 1.
Historical @全体成员 mentions were lifted as literal text; MentionAll semantics exist in the IR but the (now-squashed) backfill never produced them — only worth revisiting if mention-all lowering ever matters.
deliverContent's state machine (media fallback re-lower, empty-chunks suppression, toCompletion mapping) and the oneBot sendChunks failure matrix have no test coverage; same for the wechatpad outbound lifecycle.
Full analysis: the two review reports in the 2026-08-04 session; production repair record in docs/runbooks/adr003-cutover.md.
Tracking issue for the defects found in the post-cutover review (agents + manual verification, 2026-08-04). The legacy emote/card projection pollution was already repaired in production and is documented in
docs/runbooks/adr003-cutover.md; everything below is a runtime fix deployable at normal cadence.Ordered by priority:
retcode≠0, reactions on deleted targets) retry forever asfailed/AttemptRetryableand block the per-endpoint ordered lane behind them —retryDelaycaps the interval at 300s but nothing caps attempts (src/Max/Platform/Delivery.hsretry paths, claim ordering predicate insrc/Max/Platform/Store.hs). Convert over-budget attempts tosuppressed(content) oroutcome_unknown(undecidable) so the lane releases.claimOrWait/waitForTimelinehold a listener connection and then request a second one for the recheck; with dispatch + delivery workers + a handful of admin timeline long-polls, all pool connections become listeners and every recheck blocks on acquiring a connection with no timeout (src/Max/DB/Notify.hs). Bound admin long-poll concurrency, or run the recheck on the pinned listener connection.loadDeliveryMediatreats an unreadable blob the same as an over-limit one:error→DeliveryPermanentlyFailed, taking the message's text down with it (src/Max/Platform/Delivery.hs). A missing/unreadable blob is semantically sourceless — ADR §2 says it must fold to the text tier with a lower note; only genuine size-limit violations should stay permanent failures.insertCanonicalandverifydisagree on the rendered_text source. Ingest renders from the unenriched'Ingestbody whilemaintenance verify/reprojectrecompute from the enriched canonical body + identity map, soverifyfalse-positives (and dies) on post-cutover rows whose mention display got enriched. Render from the resolved body ininsertCanonical(src/Max/Platform/Store.hs). Until fixed, do not runmax-adr003-maintenance verifyagainst the live database.source_hash. Migration 062 changed both the hash function and its input set (055 rewrote content, 059 addedevent_kind, 060 changedoccurred_at), so every storedconversation_compartments.source_hashis stale: admin integrity checks report all historical compartments asbad_source_rangesand episode expansion flags spurious source mismatches. One offlineUPDATE … SET source_hash = conversation_source_hash(…)re-stamp.src/Max/Wechatpad.hs) so the content-match echo fallback never sees them, andlistUnconfirmedDeliveriesis only consumed by iMessage — every WeChat delivery parks ataccepted_unconfirmedforever and the admin counter grows monotonically. The synthetic native id ("sent:" <> chat <> ":" <> take 32 body) also collides for repeated identical texts, whichnativeEventAlreadyOwnedthen discards. Needs a real confirmation loop (or an honest terminal state) and a collision-free receipt id.[image#id]referencing a multi-image message emits all stored images (messageImageNodes,src/Max/ReplySend.hs) but QQ'smaxNativeMedia = 1folds every image after the first into bare[图片]text with no attachable URL for blob refs. Either raise QQ's media budget (it can send several images per message) or split lowered media across chunks.Lower priority, recorded from the same review:
status='suppressed', so poisoned messages are indistinguishable from quiet no-ops in/api/platforms/statusmonitoring without greppinglast_error.enqueueCanonicalMediaonly picks remote URLs), so models see bare[image]for non-QQ media even though the bytes are already in BlobStore.ensureConfiguredEndpointresetsendpoint_mode/caps on every boot, so a hand-promoted mirror reverts to standalone; no supported configuration lets a wechatpad room join a mirror conversation despite the code path existing.[@#<QQ号>]mention syntax and accept hallucinated numeric ids when no roster exists, minting fake principal identities on the destination account (src/Max/ReplySend.hsknown-check, prompt advertisement insrc/Max/Prompt/System.hs).max_native_media > 1on Matrix/iMessage endpoints permanently fails any multi-media chunk (ContractFailure): chunking never splits media, so the declared budget is unusable above 1.@全体成员mentions were lifted as literal text;MentionAllsemantics exist in the IR but the (now-squashed) backfill never produced them — only worth revisiting if mention-all lowering ever matters.deliverContent's state machine (media fallback re-lower, empty-chunks suppression,toCompletionmapping) and the oneBotsendChunksfailure matrix have no test coverage; same for the wechatpad outbound lifecycle.Full analysis: the two review reports in the 2026-08-04 session; production repair record in
docs/runbooks/adr003-cutover.md.