Status: Draft
Updated: 2026-05-25
Owner: Core
Purpose: Draft for recoverable error handling, validation feedback, linting workflows, and input preservation.
- Defines how user-facing workflows should recover from validation and processing errors.
- Covers form input preservation, actionable feedback, logging context, and verification/linting flows.
- Depends on the core architecture and should guide every administrative workflow.
- Establishes a shared language for recoverable failures, validation errors, preview checks, and blocking system errors.
- Keeps administrative workflows safe for humans and automation agents.
The CMS should treat recoverable errors as part of normal user-facing workflows. Invalid form input, failed preview rendering, rejected imports, failed lint checks, missing optional module contributions, and unsupported content operations should return structured feedback instead of throwing generic exceptions into the UI.
Symfony Forms, Validator constraints, CSRF protection, translation, flash messages, logs, and functional tests should remain the default toolchain. Custom result objects should be used only where a workflow needs to return several kinds of recoverable state, for example valid, invalid, requiresReview, blocked, or failedAfterSideEffect.
The main goal is to prevent data loss. When a user submits content, configuration, import operations, or editor changes, the system should keep original input available unless the operation is intentionally destructive and confirmed. Validation should explain the next corrective action in translated user-facing copy, while logs should keep developer-facing context without exposing secrets.
Hard exceptions are still appropriate for programming errors, impossible states, security violations, and infrastructure failures that cannot be safely recovered in the current request. These failures should be logged with enough context to diagnose them and should surface through Symfony's normal error handling.
- Use Symfony Forms for interactive administrative input where possible.
- Use Symfony Validator constraints for domain validation that can be expressed declaratively.
- Use custom validators for content schemas, manifests, import operations, module metadata, and theme override rules when constraints need application context.
- Keep validation and operation-feedback messages deterministic and translated in
translations/languages/{locale}/message.yaml. User-interface copy belongs in narrower source files such asui,admin,editor,setup,operations,demo, or future feature-specific catalogues; Symfony consumes generatedtranslations/runtime/{APP_ENV}/messages.{locale}.yamlruntime catalogues. - Preserve submitted input on validation failure through normal form binding or explicit draft/input snapshots for non-form workflows.
- Use CSRF protection for state-changing browser workflows.
- Use flash messages only for short workflow results. Detailed errors should remain attached to fields, operation rows, diff entries, or review panels.
- Use typed result objects for multi-step recoverable operations. Suggested states:
success: The operation completed and side effects are committed.invalid: User input or manifest data failed validation before side effects.requires_review: The operation is valid but needs explicit confirmation, for example import diffs or destructive changes.blocked: Preconditions are missing, for example incompatible module version or unavailable storage.failed: The operation started but could not complete safely.
- Keep result objects small and feature-local until a repeated pattern justifies a shared
App\Coreabstraction. - Log recoverable system context with Monolog, but keep user-facing messages concise and safe.
- Use
EventDispatcherto publish validation or workflow lifecycle events only when another feature or module has a documented reason to observe them. - Use Messenger for delayed verification, indexing, or processing steps that should not block the request.
- Provide dry-run or preview modes for high-impact workflows such as imports, module migrations, backups, restores, updates, and release packaging.
- Integrate linting and verification commands into workflows where invalid source input could break rendering or execution, for example template edits, code snippets, manifest changes, and import packages.
- Provide user-definable custom error pages for common HTTP status codes.
- Render recoverable HTTP errors through a central response renderer instead of making every caller know template paths. The fallback order is internal content entity
/system/error-pages/{status}, then@frontend/error-pages/{status}.html.twig, then@frontend/error-pages/default.html.twig. - Return the correct HTTP status code even when the body is rendered through a content entity or Twig fallback.
- Treat
401 Unauthorizedas a login-flow hint for anonymous users: render the frontend login template with an explanatory error context. Authenticated users with insufficient access should receive the normal themed error page for the chosen status. - In
APP_DEBUG, preserve Symfony's native exception page for thrown exceptions; explicit error responses may expose debug context to Twig variables when an exception was passed to the renderer. - When module or theme manifests are invalid, skip only the affected package, log the skip with error details, and continue discovering other packages.
- Store failed operation snapshots only when
APP_DEBUGis enabled. Purge these snapshots automatically when the error is resolved or debug mode is disabled. - Treat dry-run and preview checks as automated safety checks for high-impact operations. They should verify whether the proposed action is safely executable before execution and should not imply that every workflow needs a user-facing diff.
- Unit-test validators, result objects, and operation planners.
- Functional-test form submission failures to confirm input is preserved and translated messages appear.
- Test destructive and high-impact workflows in dry-run or preview mode before testing confirmed execution.
- Test that logs include useful identifiers without leaking secrets or raw sensitive payloads.
- Test invalid package manifests skip only the affected package and log useful diagnostics.
- Test debug-only failed operation snapshots are purged when resolved or when debug mode is disabled.
- Test custom error-page selection for common HTTP status codes.
- Test the fallback order from system content entity to status template to default template.
- Test that anonymous
401responses render the login template while preserving the401status code. - Run
.codex/compare_translations.phpwhen validation, flash, empty-state, or error copy changes. - Render affected routes with
php bin/console render:route /<route>after Twig or translated error display changes. - Run
php bin/console lint:containerwhen validators, event subscribers, message handlers, or services are introduced.
- Decision recorded: Treat recoverable workflow failures as explicit result states instead of exceptions.
- Decision recorded: Keep shared result abstractions minimal until at least two features need the same shape.
- Decision recorded: Use dry-run or preview checks for imports, module migrations, backups, restores, updates, and release packaging as automated safety checks before execution, without requiring a user-facing diff for every workflow.
- Decision recorded: Invalid module or theme manifests should disable only the affected package during discovery and should be logged with error details.
- Decision recorded: Failed operation snapshots are stored only when
APP_DEBUGis enabled and are automatically purged when the error is resolved or debug mode is disabled. - Decision recorded: Error handling includes user-definable custom error pages for common HTTP status codes.
- Decision recorded:
App\View\Http\HttpErrorRendererowns recoverable HTTP error presentation. Callers should return its response instead of throwing when they can safely continue the request. - Decision recorded: The HTTP error fallback order is
/system/error-pages/{status}content, then@frontend/error-pages/{status}.html.twig, then@frontend/error-pages/default.html.twig. - Decision recorded:
401for anonymous users renders the frontend login template withhttp_errorTwig variables and still returns status401; authenticated users receiving401continue through the normal system-content/status-template/default error-page fallback. - Decision recorded: Workflow issues, logs, output, and validation flows use a shared
App\Core\Message\Messageshape with a log level, machine-readable code, translatable key, parameters, and context. Core-owned message constants live in domain-owned*MessageCodeand*MessageKeycatalogues that are aggregated byApp\Core\Message\MessageCodeandApp\Core\Message\MessageKeyfor tooling and translation checks. Catalogue constant names and values are namespace-bound to their owning scope. Third-party modules and themes may provide their own deterministic package-namespaced codes and keys, but system/core namespaces remain reserved. Domain code may useMessageException::invalidArgument()to carry a structured message at hard invariant boundaries without embedding user-facing text in exceptions. - Decision recorded: Recoverable runtime, package, theme, hook, asset, and rendering failures should return
WorkflowResult/Messagediagnostics or render a controlled error response wherever possible. Hard throws are reserved for programmer errors, invalid value-object construction, setup/CLI aborts, and guard rails that cannot safely continue in-place. - Decision recorded: Message transport arrays intentionally expose only
level,code,translation_key,parameters, andcontext. They do not include a pre-renderedmessagestring before localization. - Decision recorded:
Messageis the central feedback object for logs, workflow issues, workflow messages, UI toasts, and future localized output.WorkflowResultseparates state-changing issue messages from non-blocking messages. Issues should usually describeWARN,ERROR, orEXCEPTIONdiagnostics; messages may carrySUCCESS,INFO, andDEBUGevents such as completed tasks, package discovery, process completion, and noisy filesystem details. - Decision recorded:
WARNis reserved for important but recoverable or expected fallback behavior that does not leave the system broken.ERRORmarks states that need attention or repair, including faulty packages, invalid package manifests, missing required package files, broken package dependencies, and failed writes.