feat(errors): carry a log-safe message variant on the error - #568
Merged
Conversation
Follow-up to #557, which took the wrong half of the problem. It suppressed the display in the message itself, so the caller lost it too — but whoever triggered a failed delete or restore has to know which record is meant, and a bare id does not tell them. Only a log has no business keeping the name. So `sensitiveDisplay` keeps its meaning as the model author's declaration that a display is personal data, and is consumed differently: the error now carries a second, log-safe variant of its own message. message: User "Jane Doe (jane@example.com)" (0b7e…) is not deleted. logMessage: User 0b7e… is not deleted. `logMessage` lives on the `GraphQLError` base, so every subclass inherits it. It is deliberately a class field rather than an extension: extensions are serialized to the client, so carrying it there would change the response. The throw sites state their prose once and it is rendered twice, through either the full or the log-safe display renderer (`technicalMessage`, and `forbidden` for the common case). The two variants therefore cannot drift apart, and a message naming several entities has each of them redacted — the restrict and cascade messages name two. `getTechnicalDisplay` goes back to always quoting the display, which is what 29.3.3 did; the suppression now lives in `getLogSafeTechnicalDisplay`. Consumers on 29.4.0 that set the flag get their client-facing message back and should switch to logging `logMessage`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
nicola-smartive
marked this pull request as ready for review
September 3, 2026 13:41
nicola-smartive
pushed a commit
that referenced
this pull request
Sep 3, 2026
) The release for #568 failed, and every release would have. #564 bumped conventional-changelog-conventionalcommits to 10.4.0, and 10.x cannot be rendered by the writer our release actually runs. Releases go through cycjimmy/semantic-release-action with semantic_version: 25. semantic-release@25 depends on @semantic-release/release-notes-generator@^14.1.0, 14.1.1 is the latest, and it pins conventional-changelog-writer@^8 — resolving 8.4.0. So no semantic-release upgrade escapes this today. Preset 10 plants a deliberate tripwire for exactly this mismatch: its writer options carry a `mainTemplate` whose text is the error message (@conventional-changelog/template, createLegacyWriterGuard). A modern writer has no `mainTemplate` and ignores it; a handlebars writer (<=8) compiles it, hits helperMissing and throws. Hence "Missing helper: ... requires conventional-changelog-writer@9 or newer". Neither major declares a peerDependency on the writer, so nothing warned, and nothing in the test suite touches the release path — the bump went green and broke publishing instead. Verified against the real component rather than by reading version ranges: @semantic-release/release-notes-generator@14 (writer 8.4.0) throws the exact error with preset 10.4.0, and generates correct notes with 9.3.1. renovate is told to hold the preset below 10 until release-notes-generator ships a writer@9, with the reasoning in the rule so the next person does not simply un-pin it. Co-authored-by: zwei-wealth-dev <38549277-zwei-wealth-dev@users.noreply.gitlab.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
🎉 This PR is included in version 29.6.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
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.
Why
Follow-up to #557, which fixed the wrong half of the problem.
#557 suppressed the display inside
getTechnicalDisplayitself, so it disappeared from the message the caller receives as well. That is the half that should not change: whoever triggered a failed delete or restore has to know which record is meant, and a bare id does not tell them — least of all a non-admin role, which does receive these messages. A log is the only place with no business keeping the name.What
sensitiveDisplaykeeps exactly its current meaning — the model author's declaration that a display holds personal data — and is consumed differently. The error now carries a second, log-safe variant of its own message:logMessagesits on theGraphQLErrorbase, soForbiddenError,NotFoundError,UserInputErrorandPermissionErrorall inherit it.getTechnicalDisplaygoes back to always quoting the display (29.3.3 behaviour); the suppression now lives in the newgetLogSafeTechnicalDisplay.How the throw sites stay honest
The prose is written once and rendered twice, through either display renderer:
technicalMessage(build)returns{ message, logMessage };forbidden(build)wraps that in aForbiddenError. Because both variants come from one template, they cannot drift apart — and a message that names several entities has each of them redacted, which matters for the restrict and cascade messages, since those name two.All eight display-bearing throw sites in
resolvers/mutations.tsare converted.resolvers/resolver.ts:73is left alone deliberately: it builds its display from{ id }only, so there is no display to suppress.For consumers
Anyone who adopted 29.4.0 and set the flag gets their client-facing message back, and should switch to logging
logMessage:Verification
npm run lintclean,tsc --noEmitclean, unit suite 163/163.tests/unit/technical-display.spec.tsrewritten for the new split: the caller-facing renderer keeps the display including for a sensitive model, the log-safe renderer drops it, a non-sensitive model is identical in both variants, and a two-entity message has both redacted.sensitiveDisplay, and this restores the message shape those snapshots already record.docs/docs/2-models.md) — the flag's section now describes what it does and does not affect.Note
The delete dry-run payload is untouched. Those displays are what an admin confirms a deletion against, and the dry-run is not a log.