Status: Draft
Updated: 2026-06-05 Owner: Core
Purpose: Draft for long-running admin actions, dry-runs, confirmations, action logs, job status, and recoverable operational UI.
- Defines the user-facing workflow pattern for high-impact or long-running admin operations.
- Covers dry-run review, confirmation, streamed action logs, progress states, background jobs, retries, cancellation, and recovery output.
- Depends on system theme/design system, admin interface, error handling, security/ACL, Messenger conventions, and operational feature drafts.
- Provides a shared UI and service pattern for setup, package installation, asset rebuilds, imports, exports, backups, restores, cache rebuilds, and updates.
Operational workflows should be transparent. A large admin action should not disappear behind a spinner or a vague progress bar. Users need to see what is happening, which step is running, what succeeded, what failed, and what can be retried safely.
The first operational UI should provide an interactive action log pattern. A workflow can emit structured log entries while it runs, and the admin interface displays them in a scrollable log panel with status markers, current step, completion state, and recovery actions. A progress indicator may still exist, but the log is the primary feedback channel for complex operations.
The ActionLog model is the live operation overlay first. It may later feed structured messages before responses return or jobs complete, using message levels such as success, error, warning, info, and debug. The logger should consume messages, not ActionLog internals. That future logger should be added through an explicit recorder/service boundary; generic operation-message events are deferred until the logger shape exists. Access logs, audit logs, and security logs remain separate layers even when they reuse shared message vocabulary.
The first implemented logging bridge is intentionally small: MessageReporterInterface is the direct feedback boundary for creating one structured message, reporting it, and returning it to the caller for UI/API output. WorkflowResultMessageReporterInterface receives workflow results from operation boundaries, extracts their structured messages, delegates to MessageReporterInterface, and returns the same result unchanged. OperationExecutor uses the result bridge for action results, while direct package lifecycle, setup, discovery, asset rebuild dispatch, PHP-loader, and public-hook failure boundaries use the same bridge instead of being forced through an ActionQueue. MonologMessageLogger writes messages to Monolog's message channel as translation keys plus redacted JSON context.
Messages that pass through the message layer are durable diagnostics. Transient ActionLog entries are UI transport state. A plain step without messages may disappear after the live-operation TTL, while each issue/message emitted through WorkflowResult, ActionLogEntry, or MessageReporterInterface is forwarded to the message log.
This pattern should be shared across features. Setup, imports, backups, package lifecycle changes, asset rebuilds, cache warmups, and self-updates should not each invent separate progress UIs. Each workflow may have feature-specific steps, but the shell, logging, confirmation, and result states should be consistent.
Package lifecycle workflows should include an asset alignment step when frontend contributions changed. The operational action should call assets:rebuild, mirror active package assets, rewrite generated registries, install assets, install importmap assets, run Tailwind, run AssetMapper compilation only in production, and clear cache as the finalizer so the active template/class set is reflected before normal traffic resumes.
Discovery should not activate new packages. Newly discovered packages should appear as inactive/available, then move through explicit activation workflows that validate compatibility, apply required lifecycle steps, rebuild assets where needed, and record the action log. Administrators should also have a manual "rebuild assets" action for recovery and diagnostics.
If a package activation fails during validation, contribution registration, asset sync, Tailwind build, AssetMapper compilation, or final cache clear, the workflow should restore the previous active/inactive option where practical and show the failure. This keeps lifecycle behavior simple while avoiding a complex staged compiler model.
Resolver rebuilds, collaboration exports, import dry-runs, GeoIP database updates, security-log aggregation, scheduler runs, and captcha/security diagnostics should use the same operational pattern where they are long-running or high-impact. Their reports should be suitable for both the UI action log and CLI output. Optional workflows such as GeoIP updates should become no-ops with clear log output when required configuration, such as a MaxMind API key, is missing.
- Define a shared operational action model with states such as
queued,running,requires_review,succeeded,failed,cancelled, andpartially_completed. - Define structured log entry types such as
info,step,success,warning,error,debug, anddone. - Include timestamps, step identifiers, severity, translated user-facing message keys, and safe developer context where useful.
- Keep ActionLog entries compatible with a later structured logger that can persist
success,error,warning,info, anddebugevents without turning the live overlay into the audit or access log. - Store action logs durably for high-impact operations and optionally keep transient logs for low-risk actions.
- Prefer streamed responses for short request-bound operations and Messenger-backed job status polling or server events for longer background work.
- Treat
/api/live/operations/{operationId}?token=<token>&cursor=<number>as the first implemented ActionLog polling fallback when streaming or server events are unavailable. - Return operation polling payloads with
operation_id,status,cursor, optionalcursor_max,entries, andnext_poll_ms. - Resolve live operation queues through tagged providers so Core, Admin, and later packages/updaters can add operations without turning the runner into a monolithic switch.
- Emit monotonically increasing numeric cursors from the log producer so the UI can request only entries after the last rendered cursor.
- Include a cursor maximum or comparable progress hint when the operation can estimate total steps, files, records, or work units.
- Let the server recommend polling cadence through
next_poll_ms; active logs may use short intervals, while waiting, hidden-tab, or terminal states should back off or stop. - Provide a reusable action-log panel with:
- Current step and result status.
- Scrollable live log.
- Copy/download log action.
- Retry, continue, cancel, or close actions where supported.
- Link to related entity, import batch, backup artifact, update package, or audit entry.
- Require dry-run or review phases for destructive or high-impact operations before apply.
- Keep confirmation screens explicit about what will change and what recovery path exists.
- Persist operation summaries in audit logs when the action affects content, configuration, modules, themes, users, data, backups, or release state.
- Support resolver rebuild, resolver export, import dry-run/apply, GeoIP update, security-log aggregation, and security diagnostics as first-class operational action candidates.
- Support scheduler runs as first-class operational action candidates.
bin/schedulerand/cron/runshould report due, successful, failed, disabled, and not-yet-due tasks through comparable CLI or JSON summaries. - Support package asset alignment as a first-class operational action that runs
assets:rebuild: package asset mirror and registry rewrite, translation aggregation,assets:install,importmap:install,tailwind:build, production-onlypublic/assetscleanup and AssetMapper compilation, thencache:clear. - Trigger asset alignment automatically after package activation/deactivation/update/uninstall when frontend contributions changed.
- Keep discovery separate from activation. Discovery records packages as available but inactive; activation performs runtime contribution and asset alignment.
- Roll back package lifecycle state to the previous option when activation fails before it can be safely finalized.
- Provide a manual admin "rebuild assets" action that runs
assets:rebuildthrough the operational action-log workflow. - Report asset rebuild output through the action log and keep failures recoverable where possible.
- Persist ActionLog entries before each asset rebuild step and expose current-step and planned-step counts. The UI may use streaming when available, but polling through
/api/live/operations/{operationId}?token=<token>&cursor=<number>must be able to resume if the finalcache:clearbriefly interrupts requests. - Keep operational reports serializable so the same result object can power admin UI logs, CLI summaries, and later API responses.
- Prevent sensitive values from appearing in user-visible or downloadable logs.
- Redact protected configuration values such as provider API keys from action logs, reports, and downloadable diagnostics.
- Mark queued/running live operations as failed when they stop reporting progress beyond the configured stale threshold, and periodically remove expired terminal run files.
- Claim staged live operations atomically before runner execution so duplicate console invocations cannot execute the same operation twice.
- Serialize live runner execution with one global live-operation lock for now so package lifecycle, discovery, asset rebuild, and cache-clear operations cannot mutate shared state concurrently. The lock must be released in runner finalization and stale locks must expire automatically so a crashed runner does not block future operations forever.
- Let the overlay persist the current status URL in session storage so reloads can resume polling the same operation instead of starting another run.
- Expose an Admin Operations inspection view that lists queued, running, and recently completed transient run state while the TTL has not expired, shows the current runner lock, offers expired-run cleanup, and permits clearing stale locks. An emergency kill control is allowed only for stale locks when the store has the detached runner PID and the current process command still matches the expected
operations:run <operationId>invocation. - Expose retained operation detail pages from Admin Operations while transient run state still exists. The detail page may show sanitized run metadata, ActionLog entries, and final result issues/messages, but it must not expose run tokens, submitted payloads, or other sensitive transport internals.
- Support review-required live operations by letting an operation end with
WorkflowResult::requiresReview()and a user-facing confirmation prompt issue, preferably an action-required message such asmessage.operation.action_required. The prompt must tell the user what they are being asked to continue or reject; error/exception issues alone are not valid review prompts. The runner should close that run as a terminal review-required result. The overlay should stop polling and show only Continue and Cancel. Continue must start a new tokenized operation from an explicit safe continuation descriptor supplied by the provider; Cancel only dismisses the UI and must not kill the completed runner. - Ensure operational actions enforce voters/capabilities before running and again before applying reviewed changes.
- Allow modules to contribute operational actions only through documented contracts.
- Test action state transitions for success, validation failure, runtime failure, cancellation, and partial completion.
- Test streamed log rendering and stored log retrieval where implemented.
- Test sensitive values are redacted from action logs.
- Test optional operations with missing provider configuration report a graceful no-op instead of failing hard.
- Test dry-run and confirmation behavior before destructive actions.
- Test permission checks before queueing, reviewing, applying, retrying, or cancelling actions.
- Test Messenger-backed action status updates when asynchronous processing is used.
- Test
/api/live/operations/{operationId}returns only entries newer than the supplied numeric cursor when the run token is valid. - Test operation polling payloads include
cursor_maxwhen the operation reports estimable progress. - Test terminal operation states stop or slow polling through
next_poll_ms. - Test review-required operation results stop polling, expose Continue/Cancel, and start only the provider-declared continuation operation.
- Test setup/import/backup/update workflows use the shared action-log shell instead of custom progress-only UI.
- Test discovery does not activate packages or trigger runtime contributions.
- Test explicit activation/enablement triggers validation, contribution registration, and asset alignment where needed.
- Test failed package activation restores the previous lifecycle state where practical.
- Test package lifecycle workflows run asset alignment in the expected command order.
- Test manual admin asset rebuild runs the same command sequence and reports through the action log.
- Test asset alignment failures are visible in the action log and do not silently mark the lifecycle operation successful.
- Test resolver rebuild/export, import dry-run, GeoIP update, and security-log aggregation can report through the shared action-log pattern when implemented.
- Test scheduler runs report task execution summaries through the shared action-log pattern when implemented.
- Test CLI and UI report output stay consistent for workflows that expose both surfaces.
- Decision recorded: Complex admin operations should use an interactive action log as the primary feedback pattern, not only a spinner or progress bar.
- Decision recorded: Setup, imports, exports, backups, restores, cache/asset rebuilds, package lifecycle changes, and self-updates should share one operational workflow shell.
- Decision recorded: Package lifecycle changes that affect frontend contributions automatically trigger
assets:rebuildthrough the operational action-log workflow. - Decision recorded: Asset rebuild cache clearing runs last. The operation runner must persist step state before the finalizer so live UI polling can resume by cursor if cache invalidation interrupts the transport.
- Decision recorded: Discovery only marks packages as inactive/available. Explicit activation is required before runtime contributions are registered.
- Decision recorded: Failed package activation rolls back to the previous option where practical instead of introducing a separate staged compiler model.
- Decision recorded: The admin interface should expose a manual rebuild-assets action that uses the same operational workflow as automatic lifecycle rebuilds.
- Decision recorded: Resolver rebuild/export, import dry-run/apply, GeoIP updates, security-log aggregation, and security diagnostics should use the shared operational workflow shell when they are long-running or high-impact.
- Decision recorded: Scheduler execution through
bin/schedulerand protected/cron/runshould use the shared operational workflow shell where possible. - Decision recorded: Action logs must be structured, redacted, and permission-aware.
- Decision recorded: Operational reports should be UI- and CLI-friendly.
- Decision recorded: Optional provider-backed operations such as GeoIP updates should no-op gracefully when required provider configuration is missing.
- Decision recorded: Use
/api/live/**for small application-owned JSON flows such as captcha seeds, polling endpoints, and operation status checks. These routes are separate from the versioned external/api/v1/**API surface and should use the shared JSON output renderer. - Decision recorded: ActionLog polling uses a numeric
cursorquery parameter rather than anaftertoken, and producers should exposecursor_maxwhere progress can be estimated. - Decision recorded: The ActionLog overlay uses
/api/live/operations/{operationId}?token=<token>&cursor=<number>as the first robust polling fallback when streaming is unavailable or unreliable. The route itself remains public, but the run token is required because long operations may outlive the authenticated browser session that started them. - Decision recorded: Live operation run state lives below
var/operations/{APP_ENV}so dev/test/prod runs do not share transient operation files. - Decision recorded: A staged live operation is claimed atomically by the runner before execution. A queued run can be claimed once only, so duplicate detached runner starts fail without restarting or replaying the same operation.
- Decision recorded: Live operations are serialized by a global Symfony Lock resource in the live operation store. A separate owner-token status file is touched while the runner reports progress, released in
finally, and cleaned as stale after the same 3600-second TTL used for transient operation artifacts when the underlying Symfony lock is no longer held. - Decision recorded: Live-operation and scheduler locks use
symfony/lockso operational concurrency policy is shared and portable across supported hosting environments. - Decision recorded: Live operation files are transient UI transport state, not durable logs. Completed runners may remove expired terminal runs and stale queued/running runs after a default 3600-second TTL because durable diagnostics live in the message log layer.
- Decision recorded: Admin Operations is the lightweight recovery and audit surface for transient ActionLog transport state. It may clear stale locks and expired files. It may also emergency-stop a stale runner only when the stored PID still resolves to the matching live-operation command; generic process killing stays out of scope.
- Decision recorded: Retained live-operation detail pages are admin-only diagnostics for already-stored ActionLog state. They intentionally omit polling tokens and original payload data.
- Decision recorded: The browser overlay should resume an already-started operation from the stored status URL and cursor after page reloads instead of starting a duplicate. Successful runs expose an OK action that refreshes or redirects the calling page; failed runs expose Retry and a UI-only Cancel action; running runs expose no action buttons; polling failures expose Refresh and Close for the same operation.
- Decision recorded: Review-required live operations should be represented as completed operations that request user action, not as suspended PHP processes. The continuation is a new live operation started from a provider-owned continuation descriptor, so the runner lock is released and stale locks do not depend on user interaction.
- Decision recorded: The ActionLog UI does not provide a destructive process cancellation control. Cancel and Close only dismiss or clear the overlay state; they do not kill a running operation. Retry starts a new operation after a failed terminal state.
- Decision recorded: Live operation download logs are not part of the production UI. Physical runner output is reserved for local debugging and cleanup.
- Decision recorded: Detached live-operation, scheduler, and Messenger drain processes should be started through one central detached-process service with shared output redirection, PID marker, environment, timeout, and platform-specific quoting behavior.
- Decision recorded: Dynamic view/operation diagnostics may expose richer failure context to administrators or debug contexts, but visitor-facing production UI should avoid raw exception details.
- Decision recorded: Symfony runtime translation catalogues live below
translations/runtime/{APP_ENV}/messages.{locale}.yaml; source catalogues stay undertranslations/languages/{locale}so new locales remain discoverable while local conflict files in the translation root are not loaded by Symfony. - Decision recorded: ActionLog is a live operational overlay first and may later feed a structured logger. The logger should use an explicit recorder boundary rather than a generic
OnOperationsMessageevent as its first design. - Decision recorded:
MessageReporterInterface,WorkflowResultMessageReporterInterface, andMessageLoggerInterfacenow feed Monolog'smessagechannel. Messages are written as translation keys with redacted structured context, andSUCCESSmaps tonotice,EXCEPTIONtocritical,ERRORtoerror,WARNtowarning,INFOtoinfo, andDEBUGtodebug. - Decision recorded: Access, audit, security, and operational logs are separate layers even when they share message keys, severity levels, or UI rendering patterns. The first concrete file channels are
message,audit, andaccess, each file-based with 30-day retention. - Decision recorded: Compact terminal live-operation summaries are routed through the message layer instead of a separate operation file channel. Summaries include operation id/name, status, timing, step count, message count, issue count, and continuation availability, but do not include payloads, polling tokens, or raw runner output.
- Open: Decide the primary transport for live logs: streamed HTTP response, Mercure/server-sent events, Messenger-backed status table, or polling-first.
- Open: Decide which low-risk actions can keep transient logs and which high-impact actions require durable logs.
- Open: Define the final provider capability boundary before
1.0.0. Providers should validate their own payload shape and actor intent, but the core contract should avoid pretend manifest permission flags or overly narrow restrictions that block legitimate package, editor, setup, backup, or updater flows. - Decision recorded: Target operational compatibility includes Linux, macOS, Windows, Apache, NGINX, IIS, and shared hosting that meets requirements. Untested combinations should be labelled as untested compatibility targets rather than silently treated as unsupported.
- Open: Revisit the detached runner start mechanism before production release. The current PID-file shell start is intentionally small and works for local/dev flows; production packaging may need a Symfony Process, supervisor, Messenger worker, or platform-specific runner strategy.