Repository navigation
Keep raw handler failures out of problem documents and publish the cause's own detail over the bus - #21
Merged
Conversation
added 5 commits
October 13, 2026 16:49
… message ErrorResponse::fromException() now publishes detail(), which is getMessage() by default. withDetail(?string) replaces it on the instance without touching the message the log reads; null means the exception holds no sentence written for a client.
…dled throwable A FireflyException whose detail() is null keeps its code, status, category, severity, title and extension members, but its sentence is the opaque one (with the reference) unless the deployment discloses, and then the message of the throwable underneath every such wrapper. The HTML page's authored detail reads detail() instead of getMessage().
…s as detail Command/QueryProcessingException messages read "Processing command [<class>] failed: <cause message>", and that message was the problem document's detail: a handler's QueryException put its SQL and bindings on the wire with disclosure off, and an authored refusal carried the class name in front of its sentence. The wrappers now publish the cause's own detail(), or null when the cause is not a FireflyException, so the document matches the one the same throwable gets from a controller. The message keeps the prefix for the log.
…e wire EventMapper's EventSerializationException for an event constructor that threw and ConditionEvaluator's ORCHESTRATION_CONDITION_FAILED quote an arbitrary throwable; both now withhold their detail. The orchestration REST guard publishes detail() and a fixed sentence when it is withheld, instead of any refusal's message.
The wire detail is the cause's own sentence, a non-Firefly cause is opaque unless disclosed, and the "Processing command [...] failed:" prefix stays on the wrapper's message for the log. Adds the CHANGELOG Security and Changed entries.
Merged
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.
Stops problem documents from leaking a command handler's raw failure, and makes the bus publish exactly what a controller would.
Security
A command or query whose handler threw a non-Firefly throwable produced a 500. Its
detailcontained the command's PHP class and the raw cause message, even with disclosure off (app.debug=false,firefly.web.problem.disclose=false). Reproduced through the real HTTP pipeline:The same throwable thrown from a controller is rendered as the opaque sentence (
An unexpected error occurred. It has been logged; quote reference <id>…). Bus failures now get that sentence too.OrchestrationController::guard()published any FireflyException's raw message aserror; it now publishes the authored detail or a fixed sentence. Two other wrappers that quoted raw throwables (EventMapper, orchestrationConditionEvaluator) now withhold their detail.Change
FireflyException::detail(): ?stringis the client-facing sentence, distinct fromgetMessage(), which stays the log message. It returnsgetMessage()by default.withDetail()sets it, andnullmeans "nothing authored for a client".ErrorResponse::fromException()and the HTML error page publishdetail().Processing command […] failed:prefix stays in logs only.detail()is null,ProblemMapperuses the opaque sentence, or the root cause's message if disclosure is on. It keeps the wrapper's code, status, category, title and extensions.A refused or failed command over the bus now produces the same
detailas the same exception thrown from a controller.Verification
composer check: exit 0, 6812 passed. IncludesBusFailureProblemDocumentTest(13 cases, all failing before the fix) andBusRefusalProblemDocumentTest, which now requires identical documents includingdetail.bash scripts/build-docs.sh: exit 0. Book chapters 4, 7 and 10 (EN + ES) and the error-handling, cqrs and security guides are updated.