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
- Run Server with a valid native storage-admission observation and namespace-owned external payload storage.
- Start a PHP SDK activity and hold it after the runtime grants its task lease.
- Transition storage to
draining while preserving operational write headroom.
- Return a result exceeding the namespace externalization threshold.
- 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.
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
drainingwhile preserving operational write headroom.EnforceStorageAdmission::DRAIN_ACTIONSpermits task completions but notRuntimeExternalPayloadController@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
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.