diff --git a/tools/marmot-interop/headless/patches/whitenoise-skip-unprocessable-retry.patch b/tools/marmot-interop/headless/patches/whitenoise-skip-unprocessable-retry.patch new file mode 100644 index 000000000..3d2745db9 --- /dev/null +++ b/tools/marmot-interop/headless/patches/whitenoise-skip-unprocessable-retry.patch @@ -0,0 +1,27 @@ +--- a/src/whitenoise/event_processor/account_event_processor.rs ++++ b/src/whitenoise/event_processor/account_event_processor.rs +@@ -178,7 +178,23 @@ + } + Err(e) => { + // Handle retry logic for actual processing errors +- if retry_info.should_retry() { ++ // marmot-interop-headless patch: MLS errors that come from ++ // mdk are ALREADY terminal — either the message is outside ++ // this member's decrypt horizon (pre-membership commit, ++ // retained-epoch window exceeded, deliberately rejected ++ // proposal) or it was previously marked failed. Retrying ++ // them 10 times with exponential backoff (total ~17 min) ++ // just blocks later decryptable commits behind a queue of ++ // doomed retries, so every later join / rename / leave ++ // propagation races the test timeout. Treat them all as ++ // one-shot: log once, move on. ++ let is_terminal = matches!( ++ e, ++ WhitenoiseError::MlsMessageUnprocessable(_) ++ | WhitenoiseError::MlsMessagePreviouslyFailed ++ | WhitenoiseError::MdkCoreError(_), ++ ); ++ if !is_terminal && retry_info.should_retry() { + self.schedule_retry(event, source, retry_info, e); + } else { + tracing::error!( diff --git a/tools/marmot-interop/headless/setup.sh b/tools/marmot-interop/headless/setup.sh index 797c0116b..c58658a65 100644 --- a/tools/marmot-interop/headless/setup.sh +++ b/tools/marmot-interop/headless/setup.sh @@ -39,7 +39,7 @@ preflight() { 2>&1 | tee -a "$LOG_FILE" fi - # Three harness-only patches to wnd so it runs fully offline / in + # Four harness-only patches to wnd so it runs fully offline / in # sandboxes that block outbound + kernel keyring: # 1. discovery-env: honour $WHITENOISE_DISCOVERY_RELAYS so we can # point wnd at our loopback relay instead of the baked-in public @@ -54,10 +54,18 @@ preflight() { # primal.net / nos.lol, and every later activate / publish burns # connection budget on unreachable sockets — enough to break the # account-inbox subscription plane and drop kind:1059 delivery. + # 4. skip-unprocessable-retry: when mdk-core returns + # `MlsMessageUnprocessable` (pre-membership commit, too-old epoch) + # the message is provably undecryptable — retrying it ten times + # with exponential backoff (total ~17 min) just blocks later + # decryptable commits behind a queue of doomed retries, which in + # the harness manifests as "A already left" / "name unchanged" + # timeouts. The patch treats that error as terminal. local -a patches=( "whitenoise-discovery-env.patch" "whitenoise-mock-keyring.patch" "whitenoise-defaults-env.patch" + "whitenoise-skip-unprocessable-retry.patch" ) for name in "${patches[@]}"; do local marker="$WN_REPO/.headless-patched-${name%.patch}"