fix(trader)(#30): short-TTL tarpit on proposal-timeout counterparty - #31
Open
vrogojin wants to merge 1 commit into
Open
fix(trader)(#30): short-TTL tarpit on proposal-timeout counterparty#31vrogojin wants to merge 1 commit into
vrogojin wants to merge 1 commit into
Conversation
Previously, when a counterparty went silent during NP-0 negotiation (np.propose_deal sent, np.accept_deal never arrived), the matcher would re-pick the same dead candidate on the next scan tick, burn another 30s on the proposal timeout, and starve the live candidates in the feed. Issue #30 documents the soak failure mode where a single dead acceptor monopolised alice's entire 15-minute match cycle. Fix: thread a CancellationReason through transitionDeal -> onDealCancelled so trader-main can react reason-specifically. On proposal_timeout and proposal_send_failed, call IntentEngine.recordCounterpartyFailure() to increment a per-intent failure counter; after K=2 consecutive failures the counterparty is tarpitted for 5 minutes (long enough to skip ~10 scan cycles, short enough that a recovered peer rejoins quickly). AGENT_BUSY rejection and sibling-cancellation keep the historical no-blacklist semantics — they are race-only and fully recoverable. The existing markCounterpartyFailed() (used by the W2 yield-timeout fall-through) becomes the strong-signal immediate-tarpit primitive, sharing the same failedCounterparties Map. A new clearCounterpartyFailures() resets the counter when the counterparty proves responsive (called from onDealAccepted on the proposer side) so "K consecutive" really means consecutive, not "K within the decay window regardless of intermediate successes." Tests cover: threshold gating, immediate tarpit, TTL expiry, per-intent isolation, counter decay, ID resolution (local vs market intent_id), success-reset contract, and forwarding of each cancellation reason through onDealCancelled.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #30. When a counterparty went silent during NP-0 negotiation, the matcher would deterministically re-pick the same dead candidate on every scan tick — burning the entire match cycle on a corpse while live candidates were starved. Now, after K=2 consecutive proposal failures, the counterparty is excluded from that intent's candidate pool for ~5 minutes.
What changed
CancellationReasontype;OnDealCancellednow receives a reason argument (transient — not persisted).transitionDealaccepts an optionalreasonand forwards it toonDealCancelled. Each existingtransitionDeal(_, 'CANCELLED', ...)callsite passes its semantic reason (proposal_timeout,proposal_send_failed,accept_send_failed,counterparty_rejected,sibling_cancelled,shutdown).failedCounterpartiesvalue type promoted fromSet<string>toMap<string, TarpitEntry>carryingfailureCount,tarpitUntil,lastFailureAt. NewrecordCounterpartyFailure()(soft-threshold) andclearCounterpartyFailures()(success-reset). ExistingmarkCounterpartyFailed()is now the strong-signal immediate-tarpit primitive (used by the W2 yield fall-through).onDealCancelleddiscriminates by reason: onproposal_timeout/proposal_send_failed, callrecordCounterpartyFailure. OnonDealAccepted(proposer side only), callclearCounterpartyFailuresso consecutive really means consecutive.Policy
Test plan
npm run lint— cleannpm run typecheck— cleannpm run test— 711/711 pass (10 new tests)npm run build— cleanclearCounterpartyFailures+ dedicated testTests added
src/trader/intent-engine.test.ts:markCounterpartyFailedtarpits on first callclearCounterpartyFailuresresets counterclearCounterpartyFailureson absent entry is no-opsrc/trader/negotiation-handler.test.ts:reason="proposal_timeout"on PROPOSED 30s timerreason="counterparty_rejected"on incomingnp.reject_dealreason="shutdown"oncancelPending()