Repository navigation
Keep outbox deliveries and native tasks retryable after a capacity refusal - #25
Merged
Merged
Conversation
added 9 commits
October 8, 2026 10:42
Keep the reservation that native workers bring to a connector call and drop the retry around the request work slot, which those workers no longer take. The invocation, authority, and credential retries stay. Run the native handler tests through the dispatcher's reservation, and check that a task completes while requests hold every work slot.
Keep this branch's resolution of the main merge. Native workers run from their reservation, which never refuses at entry, so the retry around taking a work slot is removed instead of wrapped around the reservation. The handler tests run through the dispatcher's reservation, and the busy-platform docs keep both capacity codes, since both are sent again.
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
A capacity rejection (
CatalogError(429, "WV-OPERATION-CAPACITY" | "WV-REQUEST-CAPACITY")) means the platform committed nothing and the call may be sent again. Two consumers still turned it into durable failures:operations/event_delivery.py,_attempt). A refusal of the transaction that records the provider version (before the POST) or of_settle(after it) fell into theexcept Exceptionfallback. That fallback re-opened, passed_checked, and settledDELIVERY_FAILED, which became an incident on the last attempt. If the re-open was refused too, the delivery became a terminalAUTHORITY_REVOKEDincident on its first attempt. It also recordedDELIVERY_FAILEDafter a receiver had acknowledged the request.connectors/execution.py). A refusal of theinvocationcheck, or of theauthorize()andcredentialscallbacks, reachedsdk/worker.py'sexcept Exception. That sentfail(HANDLER_FAILED). The kernel then openedWV-TASK-FAILEDand suspended the run, and recovery treatsHANDLER_FAILEDas non-transient, so the result was alwaysWV-TASK-AMBIGUOUSorWV-TASK-RETRIES-EXHAUSTED, even though nothing had run.Changes
capacity_rejected()andCAPACITY_CODESlive indefinitions/models.py. The commit is byte-identical to61224adfrom Keep schedules and receipts retryable after a capacity refusal #21, which is now on main, so it adds nothing after the merge.retrywithout settling. The fenced lease expires, and the next dispatch recordsACK_UNKNOWNand redelivers the same event ID. Every other error path is unchanged.attemptsincrements at claim time, anddelivery_attemptsis append-only (no DELETE grant,(delivery_id, generation)primary key). A delivery refused on every attempt ends in aDELIVERY_EXHAUSTEDincident. Refunding an attempt would need a migration or a reclaim rule, which is an owner decision and is not part of this PR._admitted()replays capacity refusals of platform calls that committed nothing.invocationcheck. The owning handler retries while its lease watchdog stays authoritative. This is the policysdk/transport.pyapplies to a remote worker's context and credentials.authorize()(the invocation check pluscredential_authority) andcredentials. Each call makes at most three attempts within one second, so a refusal cannot keep a connector's own resources open. That covers an uncommitted SQL transaction, a machine-token slot, and a quota-full audit insert that would otherwise repeat a provider read. Every attempt rechecks that the handler is still active, so a retry cannot outlive it.execute()keeps Reserve pure execution for lifespan loops and native workers #22'srequest_execution() if reservation is None else reservation.execution(). Native workers bring the dispatcher's reservation, which never refuses at entry, so this PR's retry around taking a request work slot was unreachable and is removed. The invocation, authority, and credential retries stay.Docs:
reference/integration-events.mdandreference/http-and-webhooks.md, each in a "Busy platform" paragraph. The latter now says in-process executors run up to their configuredcapacity, apart from request execution capacity.CHANGELOG (
Unreleased):leased, are recorded asACK_UNKNOWNon lease expiry, and end in aDELIVERY_EXHAUSTEDincident if refused on every attempt.HANDLER_FAILEDon a capacity refusal.capacityconfigured for eachWEAVE_NATIVE_EXECUTORSentry.email_receipts.dispatchanswers HTTP 429 for a capacity refusal where it used to answer HTTP 200 with stateblocked.Tests
The unit tests and the outbox integration test failed on the base code before the fix:
tests/unit/operations/test_outbox_capacity.py. Refusal before send, refused recheck, and refused settlement after send. It also guards that other failures still settleDELIVERY_FAILEDorAUTHORITY_REVOKED.tests/unit/connectors/test_native_handler_capacity.py. Uses the realConnectorExecutionService,ServiceTransport,Worker, and theNativeDispatcherreservation, called the way the dispatcher calls it. It covers a task that completes, including its pure work, while requests hold every work slot and the invocation check is refused once; a refused invocation check that outlasts the one-second direct window; refused authorization and credentials; the short window while a connector runs; a retry that cannot outlive the handler; and non-capacity errors that still fail. The earlier work-slot wait test is replaced, since production never takes that slot.tests/integration/test_outbox.py::test_capacity_refusal_before_send_leaves_the_lease_for_recovery. Real contention: another writer holds the project operations advisory lock past the 250 ms timeout. On the old code the delivery ended upretry/DELIVERY_FAILED. With the fix it staysleased, and on expiry it is redelivered once, with outcomes{ACK_UNKNOWN, ACK}.Verification
On the merged tree (
6af9dfc, same tree asff0569c):make check: all requested checks passed.connectors/test_postgresql_nativedid not run, because it needsWEAVE_D1_IMAGE_ID. The suites weretest_outbox,test_connector_execution,test_http_connector,test_leases,test_restart,test_terminal_capacity,test_incidents,test_schedules,test_email,connectors/test_postgresql,connectors/test_postgresql_native,providers/test_inbox, andproviders/test_teams.Follow-ups (not in this PR)
http_profiles,machine_tokens,http,postgresql,kafkaand others) still turn an exhausted capacity refusal of a callback into their own permanent failure codes.ServiceTransport.complete/failuse fixed 48-attempt/10-second windows and ignore the lease settlement scope. The remote transport retries until the task deadline instead.email_submit(email.queueandemail.execute) does not replay capacity refusals.UnitOfWorkmaps everyWQ001, including allocation quota limits, toWV-OPERATION-CAPACITY. A quota limit is not a burst.