Skip to content

Storage draining rejects payload uploads needed to finish already-leased work #146

Description

@rmcdaniel

Confirmed Failure

Server 2.3.4 + PHP SDK 2.0.8 cannot finish an already-leased activity with a large result once storage admission enters draining.

The acknowledgement endpoint is deliberately allowed to drain, but the SDK first externalizes the result through POST /api/external-payloads/v1. That request is refused before the acknowledgement can be sent:

{"reason":"storage_pressure","storage_state":"draining","retryable":true,"retry_after_seconds":5,"request_admitted":false}

HTTP status is 503. The activity remains running; repeatedly retrying the upload cannot resolve the admission mismatch. Confirmed with actual published artifacts and a valid Avro payload encoding a 1,572,608-byte result, not malformed input.

Reproduce

  1. Run Server with a valid native storage-admission observation and namespace-owned external payload storage.
  2. Start a PHP SDK activity and hold it after the runtime grants its task lease.
  3. Transition storage to draining while preserving operational write headroom.
  4. Return a result exceeding the namespace externalization threshold.
  5. Observe repeated rejected external uploads and no committed activity acknowledgement.

EnforceStorageAdmission::DRAIN_ACTIONS permits task completions but not RuntimeExternalPayloadController@store. RuntimePayloadUploads::request() uploads selected fields before sending the owning completion. The upload currently has no task-lease binding.

Existing Contract

The rejection of unbound uploads is intentional in the current admission implementation. The earlier published SDK fixture (tests/Integration/StorageAdmission/sdk/experiment.php) explicitly expects Python uploads to remain paused while PHP/Rust inline completions drain. PHP 2.0.8's automatic externalization reaches that same restriction. This is the missing safe external-result drain path, not evidence that the original inline-drain test failed or that uploads should become unrestricted.

Update that fixture across all three current SDKs when implementing the bounded extension.

Required Outcome

  • Already-leased work can finish with external payloads while storage drains, with bounded reserve consumption.
  • Do not solve this by allowing arbitrary uploads for every worker credential. Any draining exception must prove the current namespace, task/attempt, owner and an allowed completion purpose; reject expired, foreign or otherwise unbound writes.
  • Keep new workflow/input admission closed, and keep fenced/stale observations fail-closed.
  • Preserve retry/idempotency, payload integrity, acknowledgement identity and cold-read behavior.
  • Cover every affected completion/failure/command payload path and PHP, Python and Rust transport implementations. A PHP-only workaround or forcing inline transport does not solve the contract.
  • Add focused Server/SDK regressions and rerun the published-artifact leased-result scenario.

Related: #131 (original inline acknowledgement draining), #141 (HTTP upload memory). These are completed earlier changes; this issue is the interaction with automatic runtime payload uploads.

Priority: fix this correctness gap before lower-priority performance qualification. Implementation should remain a bounded extension of the existing admission and payload protocol, not a second quota or retry system.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingpriority:P1High-priority product or release risk

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions