Summary
The asynchronous flow announces success through Aether\Events\CircuitCompleted, but there is no event for the three ways a dispatched task can end without a result: the backend reports FAILED/CANCELLED (TaskFailedException), the polling budget is exhausted (QuantumExecutionException::pollingExhausted), or the task completes without counts (QuantumExecutionException::malformedResponse). In all three cases PollQuantumTask::handle() throws and the only trace is a row in failed_jobs.
Where
src/Jobs/PollQuantumTask.php:86-111 (the three failure branches, each throw with no event)
src/Events/ (only CircuitCompleted, CircuitExecuted, EntropyGenerated exist)
README.md, "Asynchronous Execution" ("Both land in failed_jobs with the task ARN in the message")
Why it is a problem
Application code that dispatches a circuit can only react to success. Reacting to a failure (notify a user, refund a credit, mark a domain record, retry with a different device) requires parsing failed_jobs, which is not an application-level API. Laravel's own asynchronous primitives expose both sides: JobFailed next to JobProcessed, NotificationFailed next to NotificationSent, MessageSent/MessageSending. A package meant to run quantum hardware from Laravel should follow the same convention: the failure of a billable remote task is a first-class domain event, not a queue-internal detail.
Suggested fix
Add Aether\Events\CircuitFailed (readonly) carrying driver, circuit, taskArn, status (TaskStatus|null) and reason (string), dispatched from PollQuantumTask immediately before each throw, mirroring how persist() is already called there. Keep the exceptions so the job still fails. Dispatch it through the injected Dispatcher like CircuitCompleted, make QuantumFake dispatch it when respondWithTaskStatus() simulates a failure, and document it in the README events table next to CircuitCompleted.
Severity: medium
Summary
The asynchronous flow announces success through
Aether\Events\CircuitCompleted, but there is no event for the three ways a dispatched task can end without a result: the backend reportsFAILED/CANCELLED(TaskFailedException), the polling budget is exhausted (QuantumExecutionException::pollingExhausted), or the task completes without counts (QuantumExecutionException::malformedResponse). In all three casesPollQuantumTask::handle()throws and the only trace is a row infailed_jobs.Where
src/Jobs/PollQuantumTask.php:86-111(the three failure branches, eachthrowwith no event)src/Events/(onlyCircuitCompleted,CircuitExecuted,EntropyGeneratedexist)README.md, "Asynchronous Execution" ("Both land infailed_jobswith the task ARN in the message")Why it is a problem
Application code that dispatches a circuit can only react to success. Reacting to a failure (notify a user, refund a credit, mark a domain record, retry with a different device) requires parsing
failed_jobs, which is not an application-level API. Laravel's own asynchronous primitives expose both sides:JobFailednext toJobProcessed,NotificationFailednext toNotificationSent,MessageSent/MessageSending. A package meant to run quantum hardware from Laravel should follow the same convention: the failure of a billable remote task is a first-class domain event, not a queue-internal detail.Suggested fix
Add
Aether\Events\CircuitFailed(readonly) carryingdriver,circuit,taskArn,status(TaskStatus|null) andreason(string), dispatched fromPollQuantumTaskimmediately before eachthrow, mirroring howpersist()is already called there. Keep the exceptions so the job still fails. Dispatch it through the injectedDispatcherlikeCircuitCompleted, makeQuantumFakedispatch it whenrespondWithTaskStatus()simulates a failure, and document it in the README events table next toCircuitCompleted.Severity: medium