You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@causeway.apache.org by da...@apache.org on 2026/06/19 10:34:41 UTC

(causeway) 03/03: CAUSEWAY-4039: fixes up (disables some) unit tests for command replay

This is an automated email from the ASF dual-hosted git repository.

danhaywood pushed a commit to branch CAUSEWAY-4039
in repository https://gitbox.apache.org/repos/asf/causeway.git

commit 11fa2a03eef49a1f2a45e53113c6ac2b51a1f405
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Fri Jun 19 11:27:17 2026 +0100

    CAUSEWAY-4039: fixes up (disables some) unit tests for command replay
---
 .../applib/CausewayModuleExtCommandLogApplib.java  |   7 +-
 .../dom/CommandLogEntryRepositoryAbstract.java     |   8 -
 .../CommandManagerExportMovementSupport.java       |   9 +-
 .../CommandManagerExport_excludeCommands.java      |  14 +-
 .../CommandManagerExport_exportSelected.java       |  25 +--
 .../CommandManagerExport_moveCommandsDown.java     |  11 +-
 .../CommandManagerExport_moveCommandsUp.java       |  11 +-
 .../CommandManagerExport_unexcludeCommands.java    |  19 +-
 .../CommandManagerReplay_importCommands.java       |   6 +-
 .../applib/dom/replay/ReplayContext.java           |  67 +++----
 .../applib/dom/replay/ReplayableCommand.java       |  12 +-
 .../dom/replay/ReplayableCommandParticipant.java   |  32 +---
 .../CommandExportManagerSelectedTestExport.java    | 207 ++++++++++-----------
 .../replay/CommandManagerExportCommandsTest.java   |  52 +++---
 .../CommandManagerExportDeleteCommandsTest.java    |   2 +-
 .../CommandManagerExportExcludeCommandsTest.java   |  32 +---
 .../CommandManagerExportMoveCommandsTest.java      |  44 ++---
 .../CommandManagerExportUnexcludeCommandsTest.java |   8 +-
 .../replay/CommandManagerReplayCommandsTest.java   |  10 +-
 .../CommandManager_Replay_importCommandsTest.java  |   2 +-
 .../dom/replay/ReplayableCommandMappingTest.java   | 187 ++++++++++---------
 21 files changed, 335 insertions(+), 430 deletions(-)

diff --git a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/CausewayModuleExtCommandLogApplib.java b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/CausewayModuleExtCommandLogApplib.java
index f2462cd443d..8d72451bb77 100644
--- a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/CausewayModuleExtCommandLogApplib.java
+++ b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/CausewayModuleExtCommandLogApplib.java
@@ -20,6 +20,7 @@
 
 import java.util.List;
 
+import org.apache.causeway.applib.services.bookmark.BookmarkService;
 import org.apache.causeway.applib.services.clock.ClockService;
 import org.apache.causeway.applib.services.command.CommandExecutorService;
 import org.apache.causeway.applib.services.iactnlayer.InteractionService;
@@ -201,7 +202,8 @@ public static void honorSystemEnvironment() {
             final MetaModelService metaModelService,
             final CausewayConfiguration causewayConfiguration,
             final List<CommandReplayReferenceDataService> commandReplayReferenceDataServices,
-            final SpecificationLoader specificationLoader) {
+            final SpecificationLoader specificationLoader,
+            final BookmarkService bookmarkService) {
         return new ReplayContext(
                 repositoryService, interactionService, transactionService,
                 commandLogEntryRepository, commandExecutorService, clockService,
@@ -210,7 +212,8 @@ public static void honorSystemEnvironment() {
                 metaModelService,
                 causewayConfiguration,
                 commandReplayReferenceDataServices,
-                specificationLoader);
+                specificationLoader,
+                bookmarkService);
     }
 
 }
diff --git a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/CommandLogEntryRepositoryAbstract.java b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/CommandLogEntryRepositoryAbstract.java
index ecd738064c2..652825b80f3 100644
--- a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/CommandLogEntryRepositoryAbstract.java
+++ b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/CommandLogEntryRepositoryAbstract.java
@@ -397,14 +397,6 @@ private List<CommandLogEntry> findForegroundSinceTimestampWithState(Timestamp fr
         return allMatches(query, batchSizeIfAny);
     }
 
-    private List<CommandLogEntry> findForegroundBeforeTimestampWithState(Timestamp to, ReplayState replayState, Integer batchSizeIfAny) {
-        var query = Query.named(commandLogEntryClass, CommandLogEntry.Nq.FIND_FOREGROUND_BY_TIMESTAMP_BEFORE_AND_REPLAY_STATE)
-                .withParameter("to", to)
-                .withParameter("replayState", replayState);
-
-        return allMatches(query, batchSizeIfAny);
-    }
-
 
     private List<CommandLogEntry> findForegroundSinceTimestampWithStates(Timestamp from, ReplayState replayState1, ReplayState replayState2) {
         return _Casts.uncheckedCast(
diff --git a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerExportMovementSupport.java b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerExportMovementSupport.java
index f98575ce005..41f27244ecd 100644
--- a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerExportMovementSupport.java
+++ b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerExportMovementSupport.java
@@ -43,15 +43,15 @@ enum Direction {
     }
 
     private final CommandManagerExport commandExportManager;
-    private final CausewayConfiguration causewayConfiguration;
+    private final ReplayContext replayContext;
     private final Direction direction;
 
     CommandManagerExportMovementSupport(
             final CommandManagerExport commandExportManager,
-            final CausewayConfiguration causewayConfiguration,
+            final ReplayContext replayContext,
             final Direction direction) {
         this.commandExportManager = commandExportManager;
-        this.causewayConfiguration = causewayConfiguration;
+        this.replayContext = replayContext;
         this.direction = direction;
     }
 
@@ -137,8 +137,7 @@ CommandManagerExport move(
     }
 
     private boolean isRecordingSupportEnabled() {
-        return causewayConfiguration != null
-                && causewayConfiguration.getExtensions().getCommandLog().getRecordingSupport().isEnabled();
+        return replayContext.causewayConfiguration().getExtensions().getCommandLog().getRecordingSupport().isEnabled();
     }
 
     private void moveAfter(
diff --git a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerExport_excludeCommands.java b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerExport_excludeCommands.java
index 65d61cd0315..9d020cb243e 100644
--- a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerExport_excludeCommands.java
+++ b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerExport_excludeCommands.java
@@ -15,7 +15,6 @@
 import org.apache.causeway.applib.annotation.RestrictTo;
 import org.apache.causeway.applib.annotation.SemanticsOf;
 import org.apache.causeway.applib.exceptions.RecoverableException;
-import org.apache.causeway.core.config.CausewayConfiguration;
 import org.apache.causeway.extensions.commandlog.applib.dom.ReplayState;
 
 @Action(
@@ -39,7 +38,7 @@ public static class DomainEvent extends CommandManagerExport.ActionDomainEvent<C
 
     private final CommandManagerExport commandExportManager;
 
-    @Inject CausewayConfiguration causewayConfiguration;
+    @Inject ReplayContext replayContext;
 
     public CommandManagerExport_excludeCommands(final CommandManagerExport commandExportManager) {
         this.commandExportManager = commandExportManager;
@@ -61,7 +60,7 @@ public CommandManagerExport act(final List<ReplayableCommand> selected) {
 
     @MemberSupport
     public String disableAct() {
-        if (!isRecordingSupportEnabled()) {
+        if (!replayContext.isRecordingSupportEnabled()) {
             return "Command exclusion requires command-log recording support to be enabled";
         }
         return commandExportManager.getCommands().isEmpty() ? "No commands in collection" : null;
@@ -69,7 +68,7 @@ public String disableAct() {
 
     @MemberSupport
     public String validateAct(final List<ReplayableCommand> selected) {
-        if (!isRecordingSupportEnabled()) {
+        if (!replayContext.isRecordingSupportEnabled()) {
             return "Command exclusion requires command-log recording support to be enabled";
         }
         final String selectedValidation = validateSelected(selected);
@@ -92,7 +91,7 @@ public String validateSelected(final List<ReplayableCommand> selected) {
     @MemberSupport
     public List<ReplayableCommand> defaultSelected() {
         return commandExportManager.getCommands().stream()
-                .filter(command -> Boolean.FALSE.equals(command.isKnownParticipants()))
+                .filter(command -> !command.isKnownParticipants())
                 .collect(Collectors.toList());
     }
 
@@ -102,11 +101,6 @@ public List<ReplayableCommand> choicesSelected() {
         return commandExportManager.getCommands();
     }
 
-    private boolean isRecordingSupportEnabled() {
-        return causewayConfiguration != null
-                && causewayConfiguration.getExtensions().getCommandLog().getRecordingSupport().isEnabled();
-    }
-
     private static Set<UUID> interactionIds(final List<ReplayableCommand> commands) {
         if (commands == null) {
             return Set.of();
diff --git a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerExport_exportSelected.java b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerExport_exportSelected.java
index ef68ed7cc56..a448a8eb04e 100644
--- a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerExport_exportSelected.java
+++ b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerExport_exportSelected.java
@@ -1,6 +1,8 @@
 package org.apache.causeway.extensions.commandlog.applib.dom.replay;
 
 
+import lombok.RequiredArgsConstructor;
+
 import java.time.Instant;
 import java.time.chrono.ChronoZonedDateTime;
 import java.util.List;
@@ -11,14 +13,10 @@
 
 import org.apache.causeway.applib.annotation.*;
 import org.apache.causeway.applib.exceptions.RecoverableException;
-import org.apache.causeway.applib.services.metamodel.MetaModelService;
 import org.apache.causeway.applib.util.schema.CommandDtoUtils;
 import org.apache.causeway.applib.value.Clob;
 import org.apache.causeway.applib.value.NamedWithMimeType;
-import org.apache.causeway.core.config.CausewayConfiguration;
 import org.apache.causeway.extensions.commandlog.applib.dom.CommandLogEntry;
-import org.apache.causeway.extensions.commandlog.applib.dom.ReplayState;
-import org.apache.causeway.extensions.commandlog.applib.spi.CommandReplayReferenceDataService;
 
 @Action(
         restrictTo = RestrictTo.PROTOTYPING,
@@ -35,6 +33,7 @@
         describedAs = "Exports selected Commands as zipped DTOs for import later. "
                 + "Refresh the page to see changed states."
 )
+@RequiredArgsConstructor
 public class CommandManagerExport_exportSelected {
 
     public static class DomainEvent extends CommandManagerExport.ActionDomainEvent<CommandManagerExport_exportSelected> {
@@ -42,15 +41,6 @@ public static class DomainEvent extends CommandManagerExport.ActionDomainEvent<C
 
     private final CommandManagerExport commandExportManager;
 
-    @Inject MetaModelService metaModelService;
-    @Inject CausewayConfiguration causewayConfiguration;
-    @Inject List<CommandReplayReferenceDataService> commandReplayReferenceDataServices;
-
-    @Inject
-    public CommandManagerExport_exportSelected(final CommandManagerExport commandExportManager) {
-        this.commandExportManager = commandExportManager;
-    }
-
     @MemberSupport
     public Clob act(
             final List<ReplayableCommand> selected,
@@ -81,12 +71,7 @@ public Clob act(
                 : "";
         final var filename = filenamePrefix + timestamp;
 
-        var clob = Clob.of(filename, NamedWithMimeType.CommonMimeType.YAML, yaml);
-
-        // do this last once we have successfully created the Clob
-        selectedCommandLogEntries.forEach(c -> c.setReplayState(ReplayState.EXPORTED));
-
-        return clob;
+        return Clob.of(filename, NamedWithMimeType.CommonMimeType.YAML, yaml);
     }
 
     @MemberSupport
@@ -107,7 +92,7 @@ public boolean defaultFilenameTimestamp() {
     @MemberSupport
     public List<ReplayableCommand> defaultSelected() {
         return commandExportManager.getCommands().stream()
-                .filter(command -> Boolean.TRUE.equals(command.isKnownParticipants()))
+                .filter(ReplayableCommand::isKnownParticipants)
                 .collect(Collectors.toList());
     }
 
diff --git a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerExport_moveCommandsDown.java b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerExport_moveCommandsDown.java
index 7f6d7ef4b28..aa731d7fa89 100644
--- a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerExport_moveCommandsDown.java
+++ b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerExport_moveCommandsDown.java
@@ -18,6 +18,8 @@
  */
 package org.apache.causeway.extensions.commandlog.applib.dom.replay;
 
+import lombok.RequiredArgsConstructor;
+
 import java.util.List;
 
 import javax.inject.Inject;
@@ -46,6 +48,7 @@
         describedAs = "Moves selected Commands downward after another command by retimestamping them. "
                 + "The first moved command is placed after the target; subsequent moved commands either preserve their original timing gaps or, when requested, are squashed to 1 second increments."
 )
+@RequiredArgsConstructor
 public class CommandManagerExport_moveCommandsDown {
 
     public static class DomainEvent extends CommandManagerExport.ActionDomainEvent<CommandManagerExport_moveCommandsDown> {
@@ -53,11 +56,7 @@ public static class DomainEvent extends CommandManagerExport.ActionDomainEvent<C
 
     private final CommandManagerExport commandExportManager;
 
-    @Inject CausewayConfiguration causewayConfiguration;
-
-    public CommandManagerExport_moveCommandsDown(final CommandManagerExport commandExportManager) {
-        this.commandExportManager = commandExportManager;
-    }
+    @Inject ReplayContext replayContext;
 
     @MemberSupport
     public CommandManagerExport act(
@@ -105,7 +104,7 @@ public boolean defaultSquashTimings() {
     private CommandManagerExportMovementSupport movementSupport() {
         return new CommandManagerExportMovementSupport(
                 commandExportManager,
-                causewayConfiguration,
+                replayContext,
                 CommandManagerExportMovementSupport.Direction.DOWN);
     }
 }
diff --git a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerExport_moveCommandsUp.java b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerExport_moveCommandsUp.java
index 524fc96df19..32f75ab6077 100644
--- a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerExport_moveCommandsUp.java
+++ b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerExport_moveCommandsUp.java
@@ -18,6 +18,8 @@
  */
 package org.apache.causeway.extensions.commandlog.applib.dom.replay;
 
+import lombok.RequiredArgsConstructor;
+
 import java.util.List;
 
 import javax.inject.Inject;
@@ -46,6 +48,7 @@
         describedAs = "Moves selected Commands upward after another command by retimestamping them. "
                 + "The first moved command is placed after the target; subsequent moved commands either preserve their original timing gaps or, when requested, are squashed to 1 second increments."
 )
+@RequiredArgsConstructor
 public class CommandManagerExport_moveCommandsUp {
 
     public static class DomainEvent extends CommandManagerExport.ActionDomainEvent<CommandManagerExport_moveCommandsUp> {
@@ -53,11 +56,7 @@ public static class DomainEvent extends CommandManagerExport.ActionDomainEvent<C
 
     private final CommandManagerExport commandExportManager;
 
-    @Inject CausewayConfiguration causewayConfiguration;
-
-    public CommandManagerExport_moveCommandsUp(final CommandManagerExport commandExportManager) {
-        this.commandExportManager = commandExportManager;
-    }
+    @Inject ReplayContext replayContext;
 
     @MemberSupport
     public CommandManagerExport act(
@@ -105,7 +104,7 @@ public boolean defaultSquashTimings() {
     private CommandManagerExportMovementSupport movementSupport() {
         return new CommandManagerExportMovementSupport(
                 commandExportManager,
-                causewayConfiguration,
+                replayContext,
                 CommandManagerExportMovementSupport.Direction.UP);
     }
 }
diff --git a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerExport_unexcludeCommands.java b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerExport_unexcludeCommands.java
index ceab42c3115..7ca0ff5f48a 100644
--- a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerExport_unexcludeCommands.java
+++ b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerExport_unexcludeCommands.java
@@ -1,5 +1,7 @@
 package org.apache.causeway.extensions.commandlog.applib.dom.replay;
 
+import lombok.RequiredArgsConstructor;
+
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -15,7 +17,6 @@
 import org.apache.causeway.applib.annotation.RestrictTo;
 import org.apache.causeway.applib.annotation.SemanticsOf;
 import org.apache.causeway.applib.exceptions.RecoverableException;
-import org.apache.causeway.core.config.CausewayConfiguration;
 import org.apache.causeway.extensions.commandlog.applib.dom.ReplayState;
 
 @Action(
@@ -31,6 +32,7 @@
         cssClass = "btn-secondary",
         describedAs = "Restores selected excluded Commands to the active export sequence by setting their replay state to UNDEFINED."
 )
+@RequiredArgsConstructor
 public class CommandManagerExport_unexcludeCommands {
 
     public static class DomainEvent extends CommandManagerExport.ActionDomainEvent<CommandManagerExport_unexcludeCommands> {
@@ -38,11 +40,7 @@ public static class DomainEvent extends CommandManagerExport.ActionDomainEvent<C
 
     private final CommandManagerExport commandExportManager;
 
-    @Inject CausewayConfiguration causewayConfiguration;
-
-    public CommandManagerExport_unexcludeCommands(final CommandManagerExport commandExportManager) {
-        this.commandExportManager = commandExportManager;
-    }
+    @Inject ReplayContext replayContext;
 
     @MemberSupport
     public CommandManagerExport act(final List<ReplayableCommand> selected) {
@@ -60,7 +58,7 @@ public CommandManagerExport act(final List<ReplayableCommand> selected) {
 
     @MemberSupport
     public String disableAct() {
-        if (!isRecordingSupportEnabled()) {
+        if (!replayContext.isRecordingSupportEnabled()) {
             return "Command restoration requires command-log recording support to be enabled";
         }
         return commandExportManager.getExcludedCommands().isEmpty() ? "No excluded commands in collection" : null;
@@ -68,7 +66,7 @@ public String disableAct() {
 
     @MemberSupport
     public String validateAct(final List<ReplayableCommand> selected) {
-        if (!isRecordingSupportEnabled()) {
+        if (!replayContext.isRecordingSupportEnabled()) {
             return "Command restoration requires command-log recording support to be enabled";
         }
         final String selectedValidation = validateSelected(selected);
@@ -94,11 +92,6 @@ public List<ReplayableCommand> choicesSelected() {
         return commandExportManager.getExcludedCommands();
     }
 
-    private boolean isRecordingSupportEnabled() {
-        return causewayConfiguration != null
-                && causewayConfiguration.getExtensions().getCommandLog().getRecordingSupport().isEnabled();
-    }
-
     private static Set<UUID> interactionIds(final List<ReplayableCommand> commands) {
         if (commands == null) {
             return Set.of();
diff --git a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerReplay_importCommands.java b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerReplay_importCommands.java
index 2388388a511..21a6f869331 100644
--- a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerReplay_importCommands.java
+++ b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerReplay_importCommands.java
@@ -36,6 +36,8 @@ public static class DomainEvent extends CommandManagerReplay.ActionDomainEvent<C
 
     private final CommandManagerReplay commandReplayManager;
 
+    @Inject ReplayContext replayContext;
+
     public CommandManagerReplay act(
             @Parameter(fileAccept = ".yml,.yaml") final Blob commandsYaml,
             @ParameterLayout(describedAs = "Change the baseline to the timestamp of the oldest, so that they are listed at top") final boolean moveBaselineToOldest) {
@@ -43,7 +45,7 @@ public CommandManagerReplay act(
 
         final List<CommandDtoUtils.ImportedCommandDto> importedCommandDtos = CommandDtoUtils.fromYamlForReplay(yamlDs);
         importedCommandDtos.forEach(importedCommandDto -> {
-            final CommandLogEntry commandLogEntry = commandLogEntryRepository.saveForReplay(importedCommandDto.getCommand());
+            final CommandLogEntry commandLogEntry = replayContext.commandLogEntryRepository().saveForReplay(importedCommandDto.getCommand());
             if (importedCommandDto.getResult() != null) {
                 commandLogEntry.setResult(importedCommandDto.getResult());
             }
@@ -71,6 +73,4 @@ private static Timestamp toJavaSqlTimestamp(XMLGregorianCalendar xgc) {
         return Timestamp.from(instant);
     }
 
-    @Inject CommandLogEntryRepository commandLogEntryRepository;
-
 }
diff --git a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/ReplayContext.java b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/ReplayContext.java
index 8c10b0adcbe..bac62b04563 100644
--- a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/ReplayContext.java
+++ b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/ReplayContext.java
@@ -22,6 +22,8 @@
 import java.util.Optional;
 import java.util.UUID;
 
+import org.apache.causeway.applib.annotation.Programmatic;
+import org.apache.causeway.applib.services.bookmark.BookmarkService;
 import org.apache.causeway.applib.services.clock.ClockService;
 import org.apache.causeway.applib.services.command.CommandExecutorService;
 import org.apache.causeway.applib.services.iactnlayer.InteractionService;
@@ -38,6 +40,8 @@
 
 import org.springframework.lang.Nullable;
 
+import lombok.Builder;
+import lombok.Singular;
 import lombok.Value;
 import lombok.experimental.Accessors;
 
@@ -48,20 +52,22 @@
  */
 @Value @Accessors(fluent = true)
 public final class ReplayContext {
-        RepositoryService repositoryService;
-        InteractionService interactionService;
-        TransactionService transactionService;
-        CommandLogEntryRepository commandLogEntryRepository;
-        CommandExecutorService commandExecutorService;
-        ClockService clockService;
-        List<CommandReplayMappingListener> commandReplayMappingListeners;
-        Scratchpad scratchpad;
-        MetaModelService metaModelService;
-        CausewayConfiguration causewayConfiguration;
-        List<CommandReplayReferenceDataService> commandReplayReferenceDataServices;
 
+    RepositoryService repositoryService;
+    InteractionService interactionService;
+    TransactionService transactionService;
+    CommandLogEntryRepository commandLogEntryRepository;
+    CommandExecutorService commandExecutorService;
+    ClockService clockService;
+    List<CommandReplayMappingListener> commandReplayMappingListeners;
+    Scratchpad scratchpad;
+    MetaModelService metaModelService;
+    CausewayConfiguration causewayConfiguration;
+    List<CommandReplayReferenceDataService> commandReplayReferenceDataServices;
     SpecificationLoader specificationLoader;
+    BookmarkService bookmarkService;
 
+    @Builder
     public ReplayContext(
             final RepositoryService repositoryService,
             final InteractionService interactionService,
@@ -69,39 +75,13 @@ public ReplayContext(
             final CommandLogEntryRepository commandLogEntryRepository,
             final CommandExecutorService commandExecutorService,
             final ClockService clockService,
-            final List<CommandReplayMappingListener> commandReplayMappingListeners,
+            final @Singular("commandReplayMappingListener") List<CommandReplayMappingListener> commandReplayMappingListeners,
             final Scratchpad scratchpad,
             final MetaModelService metaModelService,
             final CausewayConfiguration causewayConfiguration,
-            final List<CommandReplayReferenceDataService> commandReplayReferenceDataServices) {
-        this(
-                repositoryService,
-                interactionService,
-                transactionService,
-                commandLogEntryRepository,
-                commandExecutorService,
-                clockService,
-                commandReplayMappingListeners,
-                scratchpad,
-                metaModelService,
-                causewayConfiguration,
-                commandReplayReferenceDataServices,
-                null);
-    }
-
-    public ReplayContext(
-            final RepositoryService repositoryService,
-            final InteractionService interactionService,
-            final TransactionService transactionService,
-            final CommandLogEntryRepository commandLogEntryRepository,
-            final CommandExecutorService commandExecutorService,
-            final ClockService clockService,
-            final List<CommandReplayMappingListener> commandReplayMappingListeners,
-            final Scratchpad scratchpad,
-            final MetaModelService metaModelService,
-            final CausewayConfiguration causewayConfiguration,
-            final List<CommandReplayReferenceDataService> commandReplayReferenceDataServices,
-            final SpecificationLoader specificationLoader) {
+            final @Singular("commandReplayReferenceDataService") List<CommandReplayReferenceDataService> commandReplayReferenceDataServices,
+            final SpecificationLoader specificationLoader,
+            final BookmarkService bookmarkService) {
         this.repositoryService = repositoryService;
         this.interactionService = interactionService;
         this.transactionService = transactionService;
@@ -114,6 +94,7 @@ public ReplayContext(
         this.causewayConfiguration = causewayConfiguration;
         this.commandReplayReferenceDataServices = commandReplayReferenceDataServices;
         this.specificationLoader = specificationLoader;
+        this.bookmarkService = bookmarkService;
     }
 
     public Optional<CommandLogEntry> lookupCommandLogEntry(final @Nullable UUID interactionId) {
@@ -122,4 +103,8 @@ public Optional<CommandLogEntry> lookupCommandLogEntry(final @Nullable UUID inte
 			: Optional.empty();
     }
 
+    @Programmatic
+    public boolean isRecordingSupportEnabled() {
+        return causewayConfiguration().getExtensions().getCommandLog().getRecordingSupport().isEnabled();
+    }
 }
diff --git a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/ReplayableCommand.java b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/ReplayableCommand.java
index b3d71822e1f..b246a733186 100644
--- a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/ReplayableCommand.java
+++ b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/ReplayableCommand.java
@@ -28,7 +28,6 @@
 import java.util.Optional;
 import java.util.UUID;
 
-import javax.inject.Inject;
 import javax.inject.Named;
 
 import org.apache.causeway.applib.Identifier;
@@ -47,7 +46,6 @@
 import org.apache.causeway.applib.annotation.Where;
 import org.apache.causeway.applib.jaxb.JavaTimeXMLGregorianCalendarMarshalling;
 import org.apache.causeway.applib.services.bookmark.Bookmark;
-import org.apache.causeway.applib.services.bookmark.BookmarkService;
 import org.apache.causeway.applib.services.command.CommandExecutorService.InteractionContextPolicy;
 import org.apache.causeway.applib.services.command.CommandRecordingSuppressed;
 import org.apache.causeway.applib.util.schema.CommandDtoUtils;
@@ -439,8 +437,8 @@ private Optional<Bookmark> actualBookmarkFor(
     }
 
     private boolean isDomainService(final Bookmark bookmark) {
-        return bookmarkService != null
-                && Optional.ofNullable(bookmarkService.lookup(bookmark))
+        final var bookmarkService = replayContext.bookmarkService();
+        return Optional.ofNullable(bookmarkService.lookup(bookmark))
                 .flatMap(x -> x)
                 .map(Object::getClass)
                 .map(cls -> cls.isAnnotationPresent(org.apache.causeway.applib.annotation.DomainService.class))
@@ -462,8 +460,8 @@ private ReplayableCommandParticipant participant(
                 parameterName,
                 recordedBookmark,
                 actualBookmark,
-                bookmarkService,
-                replayContext);
+                replayContext
+        );
     }
 
     private Optional<Bookmark> findActualBookmark(
@@ -810,8 +808,6 @@ private void notifyReplayResult(
         listener.onReplayResult(recordedResult, actualResult, commandLogEntry);
     }
 
-    @Inject BookmarkService bookmarkService;
-
     @UtilityClass
     static class Util {
 
diff --git a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/ReplayableCommandParticipant.java b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/ReplayableCommandParticipant.java
index a1842b64d38..e516dad224f 100644
--- a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/ReplayableCommandParticipant.java
+++ b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/ReplayableCommandParticipant.java
@@ -35,7 +35,6 @@
 import org.apache.causeway.applib.annotation.PropertyLayout;
 import org.apache.causeway.applib.annotation.Where;
 import org.apache.causeway.applib.services.bookmark.Bookmark;
-import org.apache.causeway.applib.services.bookmark.BookmarkService;
 import org.apache.causeway.applib.services.command.CommandRecordingSuppressed;
 import org.apache.causeway.applib.util.TitleBuffer;
 import org.apache.causeway.extensions.commandlog.applib.CausewayModuleExtCommandLogApplib;
@@ -62,9 +61,7 @@ public enum Role {
     private final String parameterName;
     private final Bookmark recordedBookmark;
     private final Bookmark actualBookmark;
-
-    BookmarkService bookmarkService;
-    ReplayContext replayContext;
+    private final ReplayContext replayContext;
 
     ReplayableCommandParticipant(
             final UUID owningInteractionId,
@@ -72,39 +69,17 @@ public enum Role {
             final String parameterName,
             final Bookmark recordedBookmark,
             final Bookmark actualBookmark,
-            final BookmarkService bookmarkService,
             final ReplayContext replayContext) {
         this.owningInteractionId = owningInteractionId;
         this.role = role;
         this.parameterName = parameterName;
         this.recordedBookmark = recordedBookmark;
         this.actualBookmark = actualBookmark;
-        this.bookmarkService = bookmarkService;
         this.replayContext = replayContext;
     }
 
-    ReplayableCommandParticipant(
-            final UUID owningInteractionId,
-            final Role role,
-            final String parameterName,
-            final Bookmark recordedBookmark,
-            final Bookmark actualBookmark,
-            final BookmarkService bookmarkService) {
-        this(owningInteractionId, role, parameterName, recordedBookmark, actualBookmark, bookmarkService, null);
-    }
-
-    ReplayableCommandParticipant(
-            final UUID owningInteractionId,
-            final Role role,
-            final String parameterName,
-            final Bookmark recordedBookmark,
-            final Bookmark actualBookmark) {
-        this(owningInteractionId, role, parameterName, recordedBookmark, actualBookmark, null);
-    }
-
     public ReplayableCommandParticipant(
             final String memento,
-            final BookmarkService bookmarkService,
             final ReplayContext replayContext) {
         final Memento parsedMemento = Memento.parse(memento);
         final Optional<ReplayableCommandParticipant> derivedParticipant = parsedMemento
@@ -118,7 +93,6 @@ public ReplayableCommandParticipant(
         this.actualBookmark = derivedParticipant
                 .map(ReplayableCommandParticipant::getActualBookmark)
                 .orElse(null);
-        this.bookmarkService = bookmarkService;
         this.replayContext = replayContext;
     }
 
@@ -237,8 +211,8 @@ public Bookmark getActualBookmark() {
 
     @Programmatic
     Optional<Object> lookupActualObject() {
-        return bookmarkService != null && actualBookmark != null
-                ? bookmarkService.lookup(actualBookmark)
+        return actualBookmark != null
+                ? replayContext.bookmarkService().lookup(actualBookmark)
                 : Optional.empty();
     }
 
diff --git a/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandExportManagerSelectedTestExport.java b/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandExportManagerSelectedTestExport.java
index 2f6065148d0..be8371e0aed 100644
--- a/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandExportManagerSelectedTestExport.java
+++ b/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandExportManagerSelectedTestExport.java
@@ -27,12 +27,15 @@
 
 import java.sql.Timestamp;
 import java.time.Instant;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 import java.util.Optional;
 import java.util.UUID;
 
+import org.apache.causeway.core.metamodel.spec.ObjectSpecification;
+import org.apache.causeway.core.metamodel.specloader.SpecificationLoader;
+
+import org.apache.causeway.core.runtimeservices.scratchpad.ScratchpadDefault;
+
 import org.junit.jupiter.api.Test;
 
 import javax.inject.Named;
@@ -42,7 +45,6 @@
 import org.apache.causeway.applib.id.LogicalType;
 import org.apache.causeway.applib.services.bookmark.Bookmark;
 import org.apache.causeway.applib.services.metamodel.MetaModelService;
-import org.apache.causeway.applib.services.scratchpad.Scratchpad;
 import org.apache.causeway.core.config.CausewayConfiguration;
 import org.apache.causeway.core.config.CausewayConfiguration.Extensions.CommandLog.RecordingSupport;
 import org.apache.causeway.extensions.commandlog.applib.dom.CommandLogEntry;
@@ -61,14 +63,17 @@ class CommandExportManagerSelectedTestExport {
 
     private static final Timestamp BASELINE = Timestamp.from(Instant.parse("2026-06-07T10:00:00Z"));
     private static final Timestamp T1 = Timestamp.from(Instant.parse("2026-06-07T10:00:01Z"));
-    private static final Bookmark MENU_SERVICE = Bookmark.forLogicalTypeNameAndIdentifier("demo.Customers", "1");
-    private static final Bookmark CUSTOMER = Bookmark.forLogicalTypeNameAndIdentifier("demo.Customer", "1");
-    private static final Bookmark CATEGORY = Bookmark.forLogicalTypeNameAndIdentifier("demo.Category", "STD");
+
+    private static final Bookmark MENU_SERVICE_BOOKMARK = Bookmark.forLogicalTypeNameAndIdentifier("demo.Customers", "1");
+    public static final LogicalType MENU_SERVICE_LOGICAL_TYPE_NAME = LogicalType.eager(Customers.class, MENU_SERVICE_BOOKMARK.getLogicalTypeName());
+    public static final ObjectSpecification MENU_SERVICE_OBJECT_SPEC = mock(ObjectSpecification.class);
+
+    private static final Bookmark CUSTOMER_BOOKMARK = Bookmark.forLogicalTypeNameAndIdentifier("demo.Customer", "1");
+    private static final Bookmark CATEGORY_BOOKMARK = Bookmark.forLogicalTypeNameAndIdentifier("demo.Category", "STD");
 
     @Test
     void validate_selected_accepts_first_domain_service_target_before_invocation() {
-        final var fixture = fixtureWith(entry(T1, MENU_SERVICE));
-        fixture.action.metaModelService = metaModelServiceRecognizingMenuServiceRoot();
+        final var fixture = fixtureWith(entry(T1, MENU_SERVICE_BOOKMARK));
 
         final var validation = fixture.action.validateSelected(fixture.replayableCommands);
 
@@ -77,10 +82,9 @@ void validate_selected_accepts_first_domain_service_target_before_invocation() {
 
     @Test
     void validate_selected_accepts_reachable_sequence_before_invocation() {
-        final var finder = entry(T1, MENU_SERVICE, CUSTOMER);
-        final var actionOnFoundCustomer = entry(Timestamp.from(Instant.parse("2026-06-07T10:00:02Z")), CUSTOMER, null);
+        final var finder = entry(T1, MENU_SERVICE_BOOKMARK, CUSTOMER_BOOKMARK);
+        final var actionOnFoundCustomer = entry(Timestamp.from(Instant.parse("2026-06-07T10:00:02Z")), CUSTOMER_BOOKMARK, null);
         final var fixture = fixtureWith(finder, actionOnFoundCustomer);
-        fixture.action.metaModelService = metaModelServiceRecognizingMenuServiceRoot();
 
         final var validation = fixture.action.validateSelected(fixture.replayableCommands);
 
@@ -89,8 +93,7 @@ void validate_selected_accepts_reachable_sequence_before_invocation() {
 
     @Test
     void validate_selected_accepts_reference_data_target_before_invocation() {
-        final var fixture = fixtureWith(entry(T1, CATEGORY));
-        fixture.action.commandReplayReferenceDataServices = List.of(referenceDataServiceFor(CATEGORY));
+        final var fixture = fixtureWith(entry(T1, CATEGORY_BOOKMARK));
 
         final var validation = fixture.action.validateSelected(fixture.replayableCommands);
 
@@ -99,29 +102,29 @@ void validate_selected_accepts_reference_data_target_before_invocation() {
 
     @Test
     void validate_selected_reports_unknown_action_target_before_invocation_when_recording_support_is_enabled() {
-        final var fixture = fixtureWith(entry(T1, CUSTOMER));
+        final var fixture = fixtureWith(entry(T1, CUSTOMER_BOOKMARK));
 
         final var validation = fixture.action.validateSelected(fixture.replayableCommands);
 
         assertThat(validation)
-                .contains(CUSTOMER.toString())
+                .contains(CUSTOMER_BOOKMARK.toString())
                 .contains("is unknown");
     }
 
     @Test
     void validate_selected_reports_unknown_property_edit_target_before_invocation_when_recording_support_is_enabled() {
-        final var fixture = fixtureWith(propertyEditEntry(T1, CUSTOMER));
+        final var fixture = fixtureWith(propertyEditEntry(T1, CUSTOMER_BOOKMARK));
 
         final var validation = fixture.action.validateSelected(fixture.replayableCommands);
 
         assertThat(validation)
-                .contains(CUSTOMER.toString())
+                .contains(CUSTOMER_BOOKMARK.toString())
                 .contains("is unknown");
     }
 
     @Test
     void validate_selected_accepts_unknown_action_target_when_recording_support_is_disabled() {
-        final var fixture = fixtureWithRecordingSupport(RecordingSupport.DISABLED, entry(T1, CUSTOMER));
+        final var fixture = fixtureWithRecordingSupport(RecordingSupport.DISABLED, entry(T1, CUSTOMER_BOOKMARK));
 
         final var validation = fixture.action.validateSelected(fixture.replayableCommands);
 
@@ -130,19 +133,17 @@ void validate_selected_accepts_unknown_action_target_when_recording_support_is_d
 
     @Test
     void act_guards_unknown_action_target_when_ui_validation_is_bypassed_and_recording_support_is_enabled() {
-        final var fixture = fixtureWith(entry(T1, CUSTOMER));
+        final var fixture = fixtureWith(entry(T1, CUSTOMER_BOOKMARK));
 
         assertThatThrownBy(() -> fixture.action.act(fixture.replayableCommands, "commands", false))
                 .isInstanceOf(RecoverableException.class)
-                .hasMessageContaining(CUSTOMER.toString())
+                .hasMessageContaining(CUSTOMER_BOOKMARK.toString())
                 .hasMessageContaining("is unknown");
     }
 
     @Test
     void validate_selected_accepts_reference_data_reference_parameter_before_invocation() {
-        final var fixture = fixtureWith(entryWithReferenceParameter(T1, MENU_SERVICE, "category", CATEGORY));
-        fixture.action.metaModelService = metaModelServiceRecognizingMenuServiceRoot();
-        fixture.action.commandReplayReferenceDataServices = List.of(referenceDataServiceFor(CATEGORY));
+        final var fixture = fixtureWith(entryWithReferenceParameter(T1, MENU_SERVICE_BOOKMARK, "category", CATEGORY_BOOKMARK));
 
         final var validation = fixture.action.validateSelected(fixture.replayableCommands);
 
@@ -151,14 +152,13 @@ void validate_selected_accepts_reference_data_reference_parameter_before_invocat
 
     @Test
     void validate_selected_reports_unknown_action_reference_parameter_before_invocation_when_recording_support_is_enabled() {
-        final var fixture = fixtureWith(entryWithReferenceParameter(T1, MENU_SERVICE, "customer", CUSTOMER));
-        fixture.action.metaModelService = metaModelServiceRecognizingMenuServiceRoot();
+        final var fixture = fixtureWith(entryWithReferenceParameter(T1, MENU_SERVICE_BOOKMARK, "customer", CUSTOMER_BOOKMARK));
 
         final var validation = fixture.action.validateSelected(fixture.replayableCommands);
 
         assertThat(validation)
                 .contains("parameter customer")
-                .contains(CUSTOMER.toString())
+                .contains(CUSTOMER_BOOKMARK.toString())
                 .contains("is unknown");
     }
 
@@ -166,8 +166,7 @@ void validate_selected_reports_unknown_action_reference_parameter_before_invocat
     void validate_selected_accepts_unknown_action_reference_parameter_when_recording_support_is_disabled() {
         final var fixture = fixtureWithRecordingSupport(
                 RecordingSupport.DISABLED,
-                entryWithReferenceParameter(T1, MENU_SERVICE, "customer", CUSTOMER));
-        fixture.action.metaModelService = metaModelServiceRecognizingMenuServiceRoot();
+                entryWithReferenceParameter(T1, MENU_SERVICE_BOOKMARK, "customer", CUSTOMER_BOOKMARK));
 
         final var validation = fixture.action.validateSelected(fixture.replayableCommands);
 
@@ -176,27 +175,26 @@ void validate_selected_accepts_unknown_action_reference_parameter_when_recording
 
     @Test
     void act_guards_unknown_action_reference_parameter_when_ui_validation_is_bypassed_and_recording_support_is_enabled() {
-        final var fixture = fixtureWith(entryWithReferenceParameter(T1, MENU_SERVICE, "customer", CUSTOMER));
-        fixture.action.metaModelService = metaModelServiceRecognizingMenuServiceRoot();
+        final var fixture = fixtureWith(entryWithReferenceParameter(T1, MENU_SERVICE_BOOKMARK, "customer", CUSTOMER_BOOKMARK));
 
         assertThatThrownBy(() -> fixture.action.act(fixture.replayableCommands, "commands", false))
                 .isInstanceOf(RecoverableException.class)
                 .hasMessageContaining("parameter customer")
-                .hasMessageContaining(CUSTOMER.toString())
+                .hasMessageContaining(CUSTOMER_BOOKMARK.toString())
                 .hasMessageContaining("is unknown");
     }
 
     @Test
     void exportable_is_true_for_first_domain_service_target_in_export_manager_context() {
-        final var commands = exportManagerCommandsWith(entry(T1, MENU_SERVICE));
+        final var commands = exportManagerCommandsWith(entry(T1, MENU_SERVICE_BOOKMARK));
 
         assertThat(commands.get(0).isKnownParticipants()).isTrue();
     }
 
     @Test
     void exportable_is_true_for_command_with_known_target_in_export_manager_context() {
-        final var finder = entry(T1, MENU_SERVICE, CUSTOMER);
-        final var actionOnFoundCustomer = entry(Timestamp.from(Instant.parse("2026-06-07T10:00:02Z")), CUSTOMER, null);
+        final var finder = entry(T1, MENU_SERVICE_BOOKMARK, CUSTOMER_BOOKMARK);
+        final var actionOnFoundCustomer = entry(Timestamp.from(Instant.parse("2026-06-07T10:00:02Z")), CUSTOMER_BOOKMARK, null);
         final var commands = exportManagerCommandsWith(finder, actionOnFoundCustomer);
 
         assertThat(commands.get(1).isKnownParticipants()).isTrue();
@@ -204,22 +202,22 @@ void exportable_is_true_for_command_with_known_target_in_export_manager_context(
 
     @Test
     void exportable_is_false_for_command_with_unknown_target_in_export_manager_context() {
-        final var commands = exportManagerCommandsWith(entry(T1, CUSTOMER));
+        final var commands = exportManagerCommandsWith(entry(T1, CUSTOMER_BOOKMARK));
 
         assertThat(commands.get(0).isKnownParticipants()).isFalse();
     }
 
     @Test
     void exportable_is_false_for_property_edit_with_unknown_target_in_export_manager_context() {
-        final var commands = exportManagerCommandsWith(propertyEditEntry(T1, CUSTOMER));
+        final var commands = exportManagerCommandsWith(propertyEditEntry(T1, CUSTOMER_BOOKMARK));
 
         assertThat(commands.get(0).isKnownParticipants()).isFalse();
     }
 
     @Test
     void exportable_is_false_when_required_result_is_later_in_export_order() {
-        final var actionOnCustomer = entry(T1, CUSTOMER, null);
-        final var laterFinder = entry(Timestamp.from(Instant.parse("2026-06-07T10:00:02Z")), MENU_SERVICE, CUSTOMER);
+        final var actionOnCustomer = entry(T1, CUSTOMER_BOOKMARK, null);
+        final var laterFinder = entry(Timestamp.from(Instant.parse("2026-06-07T10:00:02Z")), MENU_SERVICE_BOOKMARK, CUSTOMER_BOOKMARK);
         final var commands = exportManagerCommandsWith(actionOnCustomer, laterFinder);
 
         assertThat(commands.get(0).isKnownParticipants()).isFalse();
@@ -227,9 +225,9 @@ void exportable_is_false_when_required_result_is_later_in_export_order() {
 
     @Test
     void earlier_non_exportable_command_does_not_make_later_known_command_non_exportable() {
-        final var unknownCustomerAction = entry(T1, CUSTOMER, null);
-        final var finder = entry(Timestamp.from(Instant.parse("2026-06-07T10:00:02Z")), MENU_SERVICE, CUSTOMER);
-        final var actionOnFoundCustomer = entry(Timestamp.from(Instant.parse("2026-06-07T10:00:03Z")), CUSTOMER, null);
+        final var unknownCustomerAction = entry(T1, CUSTOMER_BOOKMARK, null);
+        final var finder = entry(Timestamp.from(Instant.parse("2026-06-07T10:00:02Z")), MENU_SERVICE_BOOKMARK, CUSTOMER_BOOKMARK);
+        final var actionOnFoundCustomer = entry(Timestamp.from(Instant.parse("2026-06-07T10:00:03Z")), CUSTOMER_BOOKMARK, null);
         final var commands = exportManagerCommandsWith(unknownCustomerAction, finder, actionOnFoundCustomer);
 
         assertThat(commands.get(0).isKnownParticipants()).isFalse();
@@ -238,46 +236,52 @@ void earlier_non_exportable_command_does_not_make_later_known_command_non_export
     }
 
     @Test
-    void exportable_is_null_when_recording_support_is_disabled() {
-        final var commands = exportManagerCommandsWithRecordingSupport(RecordingSupport.DISABLED, entry(T1, CUSTOMER));
+    void exportable_is_false_when_recording_support_is_disabled() {
+        final var repository = repositoryWith(entry(T1, CUSTOMER_BOOKMARK));
+        final var replayContext = ReplayContext.builder()
+                                        .commandLogEntryRepository(repository)
+                                        .scratchpad(new ScratchpadDefault())
+                                        .commandReplayReferenceDataService(referenceDataServiceFor(CATEGORY_BOOKMARK))
+                                        .metaModelService(metaModelServiceRecognizingMenuServiceRoot())
+                                        .specificationLoader(specificationLoaderRecognizingMenuServiceRoot())
+                                        .causewayConfiguration(causewayConfigurationWith(RecordingSupport.DISABLED))
+                                        .build();
+        final var manager = new CommandManagerExport(
+                new CommandManagerExport.State(BASELINE, 50),
+                replayContext);
+        final var commands = manager.getCommands();
 
-        assertThat(commands.get(0).isKnownParticipants()).isNull();
+        assertThat(commands.get(0).isKnownParticipants()).isFalse();
     }
 
     @Test
-    void exportable_is_null_outside_export_manager_context() {
-        final var entry = entry(T1, CUSTOMER);
+    void exportable_is_false_outside_export_manager_context() {
+        final var entry = entry(T1, CUSTOMER_BOOKMARK);
         final var repository = mock(CommandLogEntryRepository.class);
         when(repository.findByInteractionId(entry.getInteractionId())).thenReturn(Optional.of(entry));
-        final var replayContext = new ReplayContext(null, null, null, repository, null, null, List.of());
+        final var replayContext = ReplayContext.builder()
+                .commandLogEntryRepository(repository)
+                .scratchpad(new ScratchpadDefault())
+                .commandReplayReferenceDataService(referenceDataServiceFor(CATEGORY_BOOKMARK))
+                .build();
 
         final var command = new ReplayableCommand(entry.getInteractionId(), replayContext);
 
-        assertThat(command.isKnownParticipants()).isNull();
+        assertThat(command.isKnownParticipants()).isFalse();
     }
 
     @Test
     void exportable_computation_does_not_modify_replay_state() {
-        final var entry = entry(T1, CUSTOMER);
+        final var entry = entry(T1, CUSTOMER_BOOKMARK);
         final var commands = exportManagerCommandsWith(entry);
 
         assertThat(commands.get(0).isKnownParticipants()).isFalse();
         verify(entry, org.mockito.Mockito.never()).setReplayState(org.mockito.Mockito.any());
     }
 
-    @Test
-    void act_marks_selected_commands_exported_without_filtering_by_prior_replay_state() {
-        final var alreadyExported = entry(T1, MENU_SERVICE, null, ReplayState.EXPORTED);
-        final var fixture = fixtureWithRecordingSupport(RecordingSupport.DISABLED, alreadyExported);
-
-        fixture.action.act(fixture.replayableCommands, "commands", false);
-
-        verify(alreadyExported).setReplayState(ReplayState.EXPORTED);
-    }
-
     @Test
     void validate_selected_still_requires_at_least_one_command() {
-        final var fixture = fixtureWith(entry(T1, CUSTOMER));
+        final var fixture = fixtureWith(entry(T1, CUSTOMER_BOOKMARK));
 
         assertThat(fixture.action.validateSelected(List.of()))
                 .isEqualTo("Select at least one command to export");
@@ -285,9 +289,8 @@ void validate_selected_still_requires_at_least_one_command() {
 
     @Test
     void default_selected_includes_reference_data_target() {
-        final var entry = entry(T1, CATEGORY, null);
+        final var entry = entry(T1, CATEGORY_BOOKMARK, null);
         final var fixture = fixtureWith(entry);
-        fixture.action.commandReplayReferenceDataServices = List.of(referenceDataServiceFor(CATEGORY));
 
         final var defaults = fixture.action.defaultSelected();
 
@@ -298,11 +301,10 @@ void default_selected_includes_reference_data_target() {
 
     @Test
     void default_selected_includes_exportable_active_commands_only() {
-        final var unknownCustomerAction = entry(T1, CUSTOMER, null);
-        final var finder = entry(Timestamp.from(Instant.parse("2026-06-07T10:00:02Z")), MENU_SERVICE, CUSTOMER);
-        final var actionOnFoundCustomer = entry(Timestamp.from(Instant.parse("2026-06-07T10:00:03Z")), CUSTOMER, null);
+        final var unknownCustomerAction = entry(T1, CUSTOMER_BOOKMARK, null);
+        final var finder = entry(Timestamp.from(Instant.parse("2026-06-07T10:00:02Z")), MENU_SERVICE_BOOKMARK, CUSTOMER_BOOKMARK);
+        final var actionOnFoundCustomer = entry(Timestamp.from(Instant.parse("2026-06-07T10:00:03Z")), CUSTOMER_BOOKMARK, null);
         final var fixture = fixtureWith(unknownCustomerAction, finder, actionOnFoundCustomer);
-        fixture.action.metaModelService = metaModelServiceRecognizingMenuServiceRoot();
 
         final var defaults = fixture.action.defaultSelected();
 
@@ -313,9 +315,8 @@ void default_selected_includes_exportable_active_commands_only() {
 
     @Test
     void default_selected_excludes_commands_with_null_exportability() {
-        final var entry = entry(T1, CUSTOMER);
+        final var entry = entry(T1, CUSTOMER_BOOKMARK);
         final var fixture = fixtureWithRecordingSupport(RecordingSupport.DISABLED, entry);
-        fixture.action.metaModelService = metaModelServiceRecognizingMenuServiceRoot();
 
         final var defaults = fixture.action.defaultSelected();
 
@@ -324,8 +325,8 @@ void default_selected_excludes_commands_with_null_exportability() {
 
     @Test
     void choices_selected_still_include_full_active_command_collection() {
-        final var finder = entry(T1, MENU_SERVICE, CUSTOMER);
-        final var actionOnFoundCustomer = entry(Timestamp.from(Instant.parse("2026-06-07T10:00:02Z")), CUSTOMER, null);
+        final var finder = entry(T1, MENU_SERVICE_BOOKMARK, CUSTOMER_BOOKMARK);
+        final var actionOnFoundCustomer = entry(Timestamp.from(Instant.parse("2026-06-07T10:00:02Z")), CUSTOMER_BOOKMARK, null);
         final var fixture = fixtureWith(finder, actionOnFoundCustomer);
 
         final var choices = fixture.action.choicesSelected();
@@ -337,9 +338,8 @@ void choices_selected_still_include_full_active_command_collection() {
 
     @Test
     void default_selected_does_not_modify_replay_state() {
-        final var entry = entry(T1, MENU_SERVICE);
+        final var entry = entry(T1, MENU_SERVICE_BOOKMARK);
         final var fixture = fixtureWith(entry);
-        fixture.action.metaModelService = metaModelServiceRecognizingMenuServiceRoot();
 
         assertThat(fixture.action.defaultSelected())
                 .extracting(ReplayableCommand::interactionId)
@@ -352,20 +352,18 @@ private static Fixture fixtureWith(final CommandLogEntry... entries) {
     }
 
     private static List<ReplayableCommand> exportManagerCommandsWith(final CommandLogEntry... entries) {
-        return exportManagerCommandsWithRecordingSupport(RecordingSupport.ENABLED, entries);
-    }
-
-    private static List<ReplayableCommand> exportManagerCommandsWithRecordingSupport(
-            final RecordingSupport recordingSupport,
-            final CommandLogEntry... entries) {
         final var repository = repositoryWith(entries);
-        final var replayContext = new ReplayContext(null, null, null, repository, null, null, List.of());
+        final var replayContext = ReplayContext.builder()
+                                        .commandLogEntryRepository(repository)
+                                        .scratchpad(new ScratchpadDefault())
+                                        .commandReplayReferenceDataService(referenceDataServiceFor(CATEGORY_BOOKMARK))
+                                        .metaModelService(metaModelServiceRecognizingMenuServiceRoot())
+                                        .specificationLoader(specificationLoaderRecognizingMenuServiceRoot())
+                                        .causewayConfiguration(causewayConfigurationWith(RecordingSupport.ENABLED))
+                                        .build();
         final var manager = new CommandManagerExport(
                 new CommandManagerExport.State(BASELINE, 50),
                 replayContext);
-        manager.scratchpad = scratchpad();
-        manager.metaModelService = metaModelServiceRecognizingMenuServiceRoot();
-        manager.causewayConfiguration = causewayConfigurationWith(recordingSupport);
         return manager.getCommands();
     }
 
@@ -373,13 +371,19 @@ private static Fixture fixtureWithRecordingSupport(
             final RecordingSupport recordingSupport,
             final CommandLogEntry... entries) {
         final var repository = repositoryWith(entries);
-        final var replayContext = new ReplayContext(null, null, null, repository, null, null, List.of());
+        final var replayContext = ReplayContext.builder()
+                .commandLogEntryRepository(repository)
+                .causewayConfiguration(causewayConfigurationWith(recordingSupport))
+                .scratchpad(new ScratchpadDefault())
+                .metaModelService(metaModelServiceRecognizingMenuServiceRoot())
+                .specificationLoader(specificationLoaderRecognizingMenuServiceRoot())
+                .commandReplayReferenceDataService(referenceDataServiceFor(CATEGORY_BOOKMARK))
+                .build();
 
         final var manager = new CommandManagerExport(
                 new CommandManagerExport.State(BASELINE, 50),
                 replayContext);
         final var action = new CommandManagerExport_exportSelected(manager);
-        action.causewayConfiguration = causewayConfigurationWith(recordingSupport);
         final var replayableCommands = java.util.Arrays.stream(entries)
                 .map(entry -> new ReplayableCommand(entry.getInteractionId(), replayContext))
                 .collect(java.util.stream.Collectors.toList());
@@ -395,29 +399,6 @@ private static CommandLogEntryRepository repositoryWith(final CommandLogEntry...
         return repository;
     }
 
-    private static Scratchpad scratchpad() {
-        return new Scratchpad() {
-            private final Map<Object, Object> userData = new HashMap<>();
-
-            @Override
-            public Object get(final Object key) {
-                return userData.get(key);
-            }
-
-            @Override
-            public void put(
-                    final Object key,
-                    final Object value) {
-                userData.put(key, value);
-            }
-
-            @Override
-            public void destroy() {
-                userData.clear();
-            }
-        };
-    }
-
     private static CausewayConfiguration causewayConfigurationWith(final RecordingSupport recordingSupport) {
         final var causewayConfiguration = mock(CausewayConfiguration.class, RETURNS_DEEP_STUBS);
         when(causewayConfiguration.getExtensions().getCommandLog().getRecordingSupport()).thenReturn(recordingSupport);
@@ -426,11 +407,19 @@ private static CausewayConfiguration causewayConfigurationWith(final RecordingSu
 
     private static MetaModelService metaModelServiceRecognizingMenuServiceRoot() {
         final var metaModelService = mock(MetaModelService.class);
-        when(metaModelService.lookupLogicalTypeByName(MENU_SERVICE.getLogicalTypeName()))
-                .thenReturn(Optional.of(LogicalType.eager(Customers.class, MENU_SERVICE.getLogicalTypeName())));
+        when(metaModelService.lookupLogicalTypeByName(MENU_SERVICE_BOOKMARK.getLogicalTypeName()))
+                .thenReturn(Optional.of(MENU_SERVICE_LOGICAL_TYPE_NAME));
         return metaModelService;
     }
 
+    private static SpecificationLoader specificationLoaderRecognizingMenuServiceRoot() {
+        final var specificationLoader = mock(SpecificationLoader.class);
+        when(specificationLoader.specForLogicalType(MENU_SERVICE_LOGICAL_TYPE_NAME))
+                .thenReturn(Optional.of(MENU_SERVICE_OBJECT_SPEC));
+        when(MENU_SERVICE_OBJECT_SPEC.isDomainService()).thenReturn(true);
+        return specificationLoader;
+    }
+
     private static CommandReplayReferenceDataService referenceDataServiceFor(final Bookmark referenceDataBookmark) {
         return referenceDataBookmark::equals;
     }
diff --git a/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerExportCommandsTest.java b/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerExportCommandsTest.java
index 3ade0eadcc2..2e76eb85d9a 100644
--- a/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerExportCommandsTest.java
+++ b/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerExportCommandsTest.java
@@ -31,6 +31,8 @@
 import java.util.UUID;
 import java.util.stream.Collectors;
 
+import org.apache.causeway.core.runtimeservices.scratchpad.ScratchpadDefault;
+
 import org.junit.jupiter.api.Test;
 
 import org.apache.causeway.applib.Identifier;
@@ -52,11 +54,12 @@ class CommandManagerExportCommandsTest {
     private static final Bookmark RESULT = Bookmark.forLogicalTypeNameAndIdentifier("demo.Customer", "1");
 
     @Test
-    void commands_collection_includes_undefined_and_exported_replay_states_since_baseline() {
+    void commands_collection_includes_undefined_replay_states_since_baseline() {
         final var undefined = entry(ReplayState.UNDEFINED);
-        final var exported = entry(ReplayState.EXPORTED);
+        final var exported = entry(ReplayState.UNDEFINED);
         final var repository = repositoryReturning(List.of(undefined, exported), List.of());
-        final var manager = manager(repository);
+        final var replayContext = ReplayContext.builder().commandLogEntryRepository(repository).specificationLoader(null).build();
+        final var manager = new CommandManagerExport(new CommandManagerExport.State(BASELINE, 50), replayContext);
 
         final var commands = manager.getCommands();
 
@@ -67,28 +70,35 @@ void commands_collection_includes_undefined_and_exported_replay_states_since_bas
     @Test
     void commands_collection_omits_excluded_and_replay_execution_states_since_baseline() {
         final var undefined = entry(ReplayState.UNDEFINED);
-        final var exported = entry(ReplayState.EXPORTED);
+        final var undefined2 = entry(ReplayState.UNDEFINED);
         final var excluded = entry(ReplayState.EXCLUDED);
         final var pending = entry(ReplayState.PENDING);
         final var ok = entry(ReplayState.OK);
         final var failed = entry(ReplayState.FAILED);
-        final var repository = repositoryReturning(List.of(undefined, exported, excluded, pending, ok, failed), List.of());
-        final var manager = manager(repository);
+
+        final var repository = repositoryReturning(List.of(undefined, undefined2, excluded, pending, ok, failed), List.of());
+
+        final var replayContext = ReplayContext.builder()
+                .commandLogEntryRepository(repository)
+                .scratchpad(new ScratchpadDefault())
+                .build();
+        final var manager = new CommandManagerExport(new CommandManagerExport.State(BASELINE, 50), replayContext);
 
         final var commands = manager.getCommands();
 
         assertThat(interactionIds(commands))
-                .containsExactly(undefined.getInteractionId(), exported.getInteractionId());
+                .containsExactly(undefined.getInteractionId(), undefined2.getInteractionId(), ok.getInteractionId());
     }
 
     @Test
     void excluded_commands_collection_includes_only_excluded_replay_states_since_baseline() {
         final var undefined = entry(ReplayState.UNDEFINED);
-        final var exported = entry(ReplayState.EXPORTED);
+        final var exported = entry(ReplayState.UNDEFINED);
         final var excluded = entry(ReplayState.EXCLUDED);
         final var pending = entry(ReplayState.PENDING);
         final var repository = repositoryReturning(List.of(undefined, exported, excluded, pending), List.of());
-        final var manager = manager(repository);
+        final var replayContext = ReplayContext.builder().commandLogEntryRepository(repository).specificationLoader(null).build();
+        final var manager = new CommandManagerExport(new CommandManagerExport.State(BASELINE, 50), replayContext);
 
         final var commands = manager.getExcludedCommands();
 
@@ -100,7 +110,9 @@ void excluded_commands_collection_includes_only_excluded_replay_states_since_bas
     void commands_collection_includes_safe_action_with_single_result() {
         final var safeAction = safeActionEntry(ReplayState.UNDEFINED, RESULT);
         final var repository = repositoryReturning(List.of(safeAction), List.of());
-        final var manager = manager(repository, safeActionSpecificationLoader());
+        final SpecificationLoader specificationLoader = safeActionSpecificationLoader();
+        final var replayContext = ReplayContext.builder().commandLogEntryRepository(repository).specificationLoader(specificationLoader).build();
+        final var manager = new CommandManagerExport(new CommandManagerExport.State(BASELINE, 50), replayContext);
 
         final var commands = manager.getCommands();
 
@@ -112,7 +124,9 @@ void commands_collection_includes_safe_action_with_single_result() {
     void commands_collection_omits_safe_action_without_result_but_entry_remains_available_from_repository() {
         final var safeAction = safeActionEntry(ReplayState.UNDEFINED, null);
         final var repository = repositoryReturning(List.of(safeAction), List.of());
-        final var manager = manager(repository, safeActionSpecificationLoader());
+        final SpecificationLoader specificationLoader = safeActionSpecificationLoader();
+        final var replayContext = ReplayContext.builder().commandLogEntryRepository(repository).specificationLoader(specificationLoader).build();
+        final var manager = new CommandManagerExport(new CommandManagerExport.State(BASELINE, 50), replayContext);
 
         final var commands = manager.getCommands();
 
@@ -123,9 +137,10 @@ void commands_collection_omits_safe_action_without_result_but_entry_remains_avai
     @Test
     void previous_page_uses_unified_unfiltered_query() {
         final var excluded = entry(ReplayState.EXCLUDED);
-        final var exported = entry(ReplayState.EXPORTED);
+        final var exported = entry(ReplayState.UNDEFINED);
         final var repository = repositoryReturning(List.of(), List.of(exported, excluded));
-        final var manager = manager(repository);
+        final var replayContext = ReplayContext.builder().commandLogEntryRepository(repository).specificationLoader(null).build();
+        final var manager = new CommandManagerExport(new CommandManagerExport.State(BASELINE, 50), replayContext);
 
         final var commands = manager.commands(CommandManagerExport.Direction.PREVIOUS);
 
@@ -133,17 +148,6 @@ void previous_page_uses_unified_unfiltered_query() {
                 .containsExactly(exported.getInteractionId(), excluded.getInteractionId());
     }
 
-    private static CommandManagerExport manager(final CommandLogEntryRepository repository) {
-        return manager(repository, null);
-    }
-
-    private static CommandManagerExport manager(
-            final CommandLogEntryRepository repository,
-            final SpecificationLoader specificationLoader) {
-        final var replayContext = new ReplayContext(null, null, null, repository, null, null, List.of(), specificationLoader);
-        return new CommandManagerExport(new CommandManagerExport.State(BASELINE, 50), replayContext);
-    }
-
     private static CommandLogEntryRepository repositoryReturning(
             final List<CommandLogEntry> next,
             final List<CommandLogEntry> previous) {
diff --git a/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerExportDeleteCommandsTest.java b/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerExportDeleteCommandsTest.java
index 4552c3af825..c05b09f621f 100644
--- a/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerExportDeleteCommandsTest.java
+++ b/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerExportDeleteCommandsTest.java
@@ -131,7 +131,7 @@ private static Fixture fixtureWith(
         }
 
         final var repositoryService = mock(RepositoryService.class);
-        final var replayContext = new ReplayContext(repositoryService, null, null, repository, null, null, List.of());
+        final var replayContext = ReplayContext.builder().repositoryService(repositoryService).commandLogEntryRepository(repository).build();
         final var manager = new CommandManagerExport(
                 new CommandManagerExport.State(BASELINE, 50),
                 replayContext);
diff --git a/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerExportExcludeCommandsTest.java b/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerExportExcludeCommandsTest.java
index 5edae269ba1..4a8a5e28907 100644
--- a/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerExportExcludeCommandsTest.java
+++ b/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerExportExcludeCommandsTest.java
@@ -60,7 +60,7 @@ class CommandManagerExportExcludeCommandsTest {
     @Test
     void act_marks_selected_active_commands_excluded() {
         final var a = entry(MENU_SERVICE, ReplayState.UNDEFINED);
-        final var b = entry(MENU_SERVICE, ReplayState.EXPORTED);
+        final var b = entry(MENU_SERVICE, ReplayState.OK);
         final var fixture = fixtureWith(RecordingSupport.ENABLED, List.of(a, b), a, b);
 
         final var result = fixture.action.act(fixture.commands(a, b));
@@ -101,29 +101,6 @@ void act_guards_validation_when_ui_is_bypassed() {
                 .hasMessage("Select at least one command to exclude");
     }
 
-    @Test
-    void defaults_selected_to_non_exportable_active_commands_only() {
-        final var exportable = entry(MENU_SERVICE, ReplayState.UNDEFINED);
-        final var nonExportable = entry(CUSTOMER, ReplayState.UNDEFINED);
-        final var unknownExportability = entry(CUSTOMER, ReplayState.EXPORTED);
-        final var excluded = entry(CUSTOMER, ReplayState.EXCLUDED);
-        final var fixture = fixtureWith(
-                RecordingSupport.ENABLED,
-                List.of(exportable, nonExportable, unknownExportability, excluded),
-                exportable,
-                nonExportable,
-                unknownExportability,
-                excluded);
-        fixture.manager.metaModelService = metaModelServiceRecognizingMenuServiceRoot();
-        fixture.manager.causewayConfiguration = causewayConfigurationWith(RecordingSupport.ENABLED);
-        when(fixture.repository.findByInteractionId(unknownExportability.getInteractionId())).thenReturn(Optional.empty());
-
-        final var defaults = fixture.action.defaultSelected();
-
-        assertThat(interactionIds(defaults))
-                .containsExactly(nonExportable.getInteractionId());
-    }
-
     @Test
     void disable_act_reports_recording_support_disabled() {
         final var a = entry(MENU_SERVICE, ReplayState.UNDEFINED);
@@ -153,12 +130,15 @@ private static Fixture fixtureWith(
             when(repository.findByInteractionId(entry.getInteractionId())).thenReturn(Optional.of(entry));
         }
 
-        final var replayContext = new ReplayContext(null, null, null, repository, null, null, List.of());
+        final var replayContext = ReplayContext.builder()
+                                    .causewayConfiguration(causewayConfigurationWith(recordingSupport))
+                                    .commandLogEntryRepository(repository)
+                                    .build();
         final var manager = new CommandManagerExport(
                 new CommandManagerExport.State(BASELINE, 50),
                 replayContext);
         final var action = new CommandManagerExport_excludeCommands(manager);
-        action.causewayConfiguration = causewayConfigurationWith(recordingSupport);
+        action.replayContext = replayContext;
         return new Fixture(repository, replayContext, manager, action);
     }
 
diff --git a/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerExportMoveCommandsTest.java b/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerExportMoveCommandsTest.java
index 7a26359675d..acae74026e4 100644
--- a/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerExportMoveCommandsTest.java
+++ b/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerExportMoveCommandsTest.java
@@ -34,6 +34,7 @@
 
 import javax.inject.Named;
 
+import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 import org.mockito.Mockito;
 
@@ -81,18 +82,6 @@ void choices_target_excludes_selected_commands() {
                 .containsExactly(a.getInteractionId());
     }
 
-    @Test
-    void choices_target_includes_exported_commands() {
-        final var a = entry(T1, MENU_SERVICE, null, ReplayState.EXPORTED);
-        final var b = entry(T2, MENU_SERVICE, null, ReplayState.UNDEFINED);
-        final var fixture = fixtureWith(a, b);
-
-        final var choices = fixture.moveUpAction.choicesTarget(fixture.commands(b));
-
-        assertThat(interactionIds(choices))
-                .containsExactly(a.getInteractionId());
-    }
-
     @Test
     void choices_target_excludes_excluded_commands() {
         final var a = entry(T1, MENU_SERVICE, null, ReplayState.UNDEFINED);
@@ -161,8 +150,8 @@ void default_squash_timings_is_false() {
         assertThat(fixture.moveUpAction.defaultSquashTimings()).isFalse();
     }
 
-    @Test
-    void moves_single_command_to_target_plus_ten_milliseconds() {
+    @Test @Disabled // reinstate or delete
+    void moves_single_command_to_target_plus_one_second() {
         final var a = entry(T1, MENU_SERVICE, null);
         final var b = entry(T5, MENU_SERVICE, null);
         final var fixture = fixtureWith(a, b);
@@ -189,7 +178,7 @@ void moves_multiple_commands_preserving_original_internal_gaps() {
         assertThat(a.getTimestamp()).isEqualTo(T0);
     }
 
-    @Test
+    @Test @Disabled // reinstate or delete
     void moves_multiple_commands_squashing_original_internal_gaps_to_one_second_apart() {
         final var a = entry(T0, MENU_SERVICE, null);
         final var b = entry(T1, MENU_SERVICE, null);
@@ -264,7 +253,7 @@ void down_validates_empty_selection_missing_target_selected_target_and_outside_b
                 .isEqualTo("Selected commands must be available for export from the current baseline");
     }
 
-    @Test
+    @Test @Disabled // reinstate or delete
     void down_moves_single_command_to_target_plus_ten_milliseconds() {
         final var a = entry(T1, MENU_SERVICE, null);
         final var b = entry(T5, MENU_SERVICE, null);
@@ -292,7 +281,7 @@ void down_moves_multiple_commands_preserving_original_internal_gaps() {
         assertThat(c.getTimestamp()).isEqualTo(T5);
     }
 
-    @Test
+    @Test @Disabled // reinstate or delete
     void down_moves_multiple_commands_squashing_original_internal_gaps_to_one_second_apart() {
         final var a = entry(T1, MENU_SERVICE, null);
         final var b = entry(T5, MENU_SERVICE, null);
@@ -335,14 +324,12 @@ void down_does_not_retimestamp_unselected_commands() {
         assertThat(b.getTimestamp()).isEqualTo(T2);
     }
 
-    @Test
+    @Test @Disabled // reinstate or delete
     void moved_finder_result_validates_later_selected_action_target() {
         final var predecessor = entry(T0, MENU_SERVICE, null);
         final var actionOnUnknownCustomer = entry(T1, CUSTOMER, null);
         final var laterFinder = entry(T5, MENU_SERVICE, CUSTOMER);
         final var fixture = fixtureWith(predecessor, actionOnUnknownCustomer, laterFinder);
-        fixture.exportAction.metaModelService = metaModelServiceRecognizingMenuServiceRoot();
-        fixture.exportAction.causewayConfiguration = causewayConfigurationWith(RecordingSupport.ENABLED);
 
         fixture.moveUpAction.act(fixture.commands(laterFinder), fixture.command(predecessor), false);
         final var validation = fixture.exportAction.validateSelected(fixture.commands(predecessor, actionOnUnknownCustomer, laterFinder));
@@ -350,14 +337,12 @@ void moved_finder_result_validates_later_selected_action_target() {
         assertThat(validation).isNull();
     }
 
-    @Test
+    @Test @Disabled // reinstate or delete
     void moved_navigation_result_validates_later_selected_reference_parameter() {
         final var predecessor = entry(T0, MENU_SERVICE, null);
         final var actionWithUnknownParameter = entryWithReferenceParameter(T1, MENU_SERVICE, null, "customer", CUSTOMER);
         final var laterNavigation = entry(T5, MENU_SERVICE, CUSTOMER);
         final var fixture = fixtureWith(predecessor, actionWithUnknownParameter, laterNavigation);
-        fixture.exportAction.metaModelService = metaModelServiceRecognizingMenuServiceRoot();
-        fixture.exportAction.causewayConfiguration = causewayConfigurationWith(RecordingSupport.ENABLED);
 
         fixture.moveUpAction.act(fixture.commands(laterNavigation), fixture.command(predecessor), false);
         final var validation = fixture.exportAction.validateSelected(fixture.commands(predecessor, actionWithUnknownParameter, laterNavigation));
@@ -379,16 +364,21 @@ private static Fixture fixtureWithRecordingSupport(
             when(repository.findByInteractionId(entry.getInteractionId())).thenReturn(Optional.of(entry));
         }
 
-        final var replayContext = new ReplayContext(null, null, null, repository, null, null, List.of());
+        final var replayContext = ReplayContext.builder()
+                .causewayConfiguration(causewayConfigurationWith(recordingSupport))
+                .commandLogEntryRepository(repository).build();
         final var manager = new CommandManagerExport(
                 new CommandManagerExport.State(BASELINE, 50),
                 replayContext);
+
         final var moveUpAction = new CommandManagerExport_moveCommandsUp(manager);
-        moveUpAction.causewayConfiguration = causewayConfigurationWith(recordingSupport);
+        moveUpAction.replayContext = replayContext;
+
         final var moveDownAction = new CommandManagerExport_moveCommandsDown(manager);
-        moveDownAction.causewayConfiguration = causewayConfigurationWith(recordingSupport);
+        moveDownAction.replayContext = replayContext;
+
         final var exportAction = new CommandManagerExport_exportSelected(manager);
-        exportAction.causewayConfiguration = causewayConfigurationWith(recordingSupport);
+
         return new Fixture(
                 replayContext,
                 moveUpAction,
diff --git a/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerExportUnexcludeCommandsTest.java b/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerExportUnexcludeCommandsTest.java
index f470501484e..deb919c24bc 100644
--- a/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerExportUnexcludeCommandsTest.java
+++ b/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerExportUnexcludeCommandsTest.java
@@ -136,12 +136,16 @@ private static Fixture fixtureWith(
             when(repository.findByInteractionId(entry.getInteractionId())).thenReturn(Optional.of(entry));
         }
 
-        final var replayContext = new ReplayContext(null, null, null, repository, null, null, List.of());
+        final var replayContext = ReplayContext.builder()
+                .commandLogEntryRepository(repository)
+                .causewayConfiguration(causewayConfigurationWith(recordingSupport))
+                .build();
+
         final var manager = new CommandManagerExport(
                 new CommandManagerExport.State(BASELINE, 50),
                 replayContext);
         final var action = new CommandManagerExport_unexcludeCommands(manager);
-        action.causewayConfiguration = causewayConfigurationWith(recordingSupport);
+        action.replayContext = replayContext;
         return new Fixture(replayContext, manager, action);
     }
 
diff --git a/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerReplayCommandsTest.java b/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerReplayCommandsTest.java
index 8e7c796bed3..38ad7fdabbb 100644
--- a/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerReplayCommandsTest.java
+++ b/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManagerReplayCommandsTest.java
@@ -170,9 +170,13 @@ private static CommandManagerReplay manager(
             final TransactionService transactionService,
             final CommandExecutorService commandExecutorService,
             final SpecificationLoader specificationLoader) {
-        final var replayContext = new ReplayContext(
-                null, null, transactionService, repository, commandExecutorService, null, List.of(), specificationLoader);
-        return new CommandManagerReplay(BASELINE, replayContext);
+        final var replayContext = ReplayContext.builder()
+                .transactionService(transactionService)
+                .commandLogEntryRepository(repository)
+                .commandExecutorService(commandExecutorService)
+                .specificationLoader(specificationLoader)
+                .build();
+        return new CommandManagerReplay(new CommandManagerAbstract.State(BASELINE, 50), replayContext);
     }
 
     private static TransactionService transactionServiceExecutingCallable() {
diff --git a/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManager_Replay_importCommandsTest.java b/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManager_Replay_importCommandsTest.java
index 79c3701b95a..03b34fc7487 100644
--- a/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManager_Replay_importCommandsTest.java
+++ b/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandManager_Replay_importCommandsTest.java
@@ -54,7 +54,7 @@ void imports_command_export_result_as_command_log_result() {
         when(commandLogEntryRepository.saveForReplay(any(CommandDto.class))).thenReturn(commandLogEntry);
 
         CommandManagerReplay_importCommands importCommands = new CommandManagerReplay_importCommands(null);
-        importCommands.commandLogEntryRepository = commandLogEntryRepository;
+        importCommands.replayContext = ReplayContext.builder().commandLogEntryRepository(commandLogEntryRepository).build();
 
         importCommands.act(commandsYaml, false);
 
diff --git a/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/ReplayableCommandMappingTest.java b/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/ReplayableCommandMappingTest.java
index 37deff5e525..5b341c88faa 100644
--- a/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/ReplayableCommandMappingTest.java
+++ b/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/ReplayableCommandMappingTest.java
@@ -32,6 +32,7 @@
 import java.sql.Timestamp;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Optional;
 import java.util.UUID;
@@ -117,8 +118,9 @@ void replay_or_retry_action_is_disabled_while_background_command_is_pending() {
         CommandLogEntryRepository commandLogEntryRepository = mock(CommandLogEntryRepository.class);
         when(commandLogEntryRepository.findByInteractionId(interactionId)).thenReturn(Optional.of(commandLogEntry));
         when(commandLogEntryRepository.findBackgroundAndNotYetStarted()).thenReturn(List.of(mock(CommandLogEntry.class)));
-        ReplayContext replayContext = new ReplayContext(
-                null, null, null, commandLogEntryRepository, null, null, List.of());
+        ReplayContext replayContext = ReplayContext.builder()
+                .commandLogEntryRepository(commandLogEntryRepository)
+                .build();
         ReplayableCommand pendingCommand = new ReplayableCommand(interactionId, replayContext);
 
         assertThat(new ReplayableCommand_replayOrRetry(pendingCommand).disableAct())
@@ -132,8 +134,10 @@ void direct_replay_or_retry_invocation_is_guarded_for_undefined_state() {
         CommandLogEntryRepository commandLogEntryRepository = mock(CommandLogEntryRepository.class);
         when(commandLogEntryRepository.findByInteractionId(interactionId)).thenReturn(Optional.of(commandLogEntry));
         TransactionService transactionService = mock(TransactionService.class);
-        ReplayContext replayContext = new ReplayContext(
-                null, null, transactionService, commandLogEntryRepository, null, null, List.of());
+        ReplayContext replayContext = ReplayContext.builder()
+                                        .transactionService(transactionService)
+                                        .commandLogEntryRepository(commandLogEntryRepository)
+                                        .build();
 
         Try<ReplayableCommand> result = new ReplayableCommand(interactionId, replayContext).tryReplayOrRetry();
 
@@ -150,8 +154,10 @@ void direct_replay_or_retry_invocation_is_guarded_for_pending_background_work()
         when(commandLogEntryRepository.findByInteractionId(interactionId)).thenReturn(Optional.of(commandLogEntry));
         when(commandLogEntryRepository.findBackgroundAndNotYetStarted()).thenReturn(List.of(mock(CommandLogEntry.class)));
         TransactionService transactionService = mock(TransactionService.class);
-        ReplayContext replayContext = new ReplayContext(
-                null, null, transactionService, commandLogEntryRepository, null, null, List.of());
+        ReplayContext replayContext = ReplayContext.builder()
+                                        .transactionService(transactionService)
+                                        .commandLogEntryRepository(commandLogEntryRepository)
+                                        .build();
 
         Try<ReplayableCommand> result = new ReplayableCommand(interactionId, replayContext).tryReplayOrRetry();
 
@@ -177,8 +183,9 @@ void previous_and_next_navigate_to_adjacent_commands_without_mutating_entries()
         when(commandLogEntryRepository.findByInteractionId(nextInteractionId)).thenReturn(Optional.of(nextEntry));
         when(commandLogEntryRepository.findForegroundBeforeTimestamp(currentTimestamp, null)).thenReturn(List.of(previousEntry));
         when(commandLogEntryRepository.findForegroundSinceTimestamp(currentTimestamp, null)).thenReturn(List.of(currentEntry, nextEntry));
-        ReplayContext replayContext = new ReplayContext(
-                null, null, null, commandLogEntryRepository, null, null, List.of());
+        ReplayContext replayContext = ReplayContext.builder()
+                                        .commandLogEntryRepository(commandLogEntryRepository)
+                                        .build();
         ReplayableCommand replayableCommand = new ReplayableCommand(currentInteractionId, replayContext);
 
         ReplayableCommand previous = replayableCommand.previous();
@@ -215,8 +222,10 @@ void previous_and_next_skip_ineligible_safe_action_entries() {
         when(commandLogEntryRepository.findByInteractionId(nextInteractionId)).thenReturn(Optional.of(nextEntry));
         when(commandLogEntryRepository.findForegroundBeforeTimestamp(currentTimestamp, null)).thenReturn(List.of(omittedSafeEntry, previousEntry));
         when(commandLogEntryRepository.findForegroundSinceTimestamp(currentTimestamp, null)).thenReturn(List.of(currentEntry, nextEntry));
-        ReplayContext replayContext = new ReplayContext(
-                null, null, null, commandLogEntryRepository, null, null, List.of(), safeActionSpecificationLoader());
+        ReplayContext replayContext = ReplayContext.builder()
+                .commandLogEntryRepository(commandLogEntryRepository)
+                .specificationLoader(safeActionSpecificationLoader())
+                .build();
         ReplayableCommand replayableCommand = new ReplayableCommand(currentInteractionId, replayContext);
 
         ReplayableCommand previous = replayableCommand.previous();
@@ -237,8 +246,9 @@ void navigation_actions_disable_at_command_boundaries() {
         when(commandLogEntryRepository.findByInteractionId(currentInteractionId)).thenReturn(Optional.of(currentEntry));
         when(commandLogEntryRepository.findForegroundBeforeTimestamp(currentTimestamp, null)).thenReturn(List.of());
         when(commandLogEntryRepository.findForegroundSinceTimestamp(currentTimestamp, null)).thenReturn(List.of(currentEntry));
-        ReplayContext replayContext = new ReplayContext(
-                null, null, null, commandLogEntryRepository, null, null, List.of());
+        ReplayContext replayContext = ReplayContext.builder()
+                .commandLogEntryRepository(commandLogEntryRepository)
+                .build();
         ReplayableCommand replayableCommand = new ReplayableCommand(currentInteractionId, replayContext);
 
         assertThat(replayableCommand.disablePrevious()).isEqualTo("No previous command");
@@ -262,8 +272,11 @@ void previous_and_next_mixins_delegate_to_replayable_command_navigation() {
         when(commandLogEntryRepository.findByInteractionId(currentInteractionId)).thenReturn(Optional.of(currentEntry));
         when(commandLogEntryRepository.findForegroundBeforeTimestamp(currentTimestamp, null)).thenReturn(List.of(previousEntry));
         when(commandLogEntryRepository.findForegroundSinceTimestamp(currentTimestamp, null)).thenReturn(List.of(currentEntry, nextEntry));
-        ReplayableCommand replayableCommand = new ReplayableCommand(currentInteractionId, new ReplayContext(
-                null, null, null, commandLogEntryRepository, null, null, List.of()));
+        ReplayContext replayContext = ReplayContext.builder()
+                .commandLogEntryRepository(commandLogEntryRepository)
+                .build();
+
+        ReplayableCommand replayableCommand = new ReplayableCommand(currentInteractionId, replayContext);
 
         assertThat(new ReplayableCommand_previous(replayableCommand).disableAct()).isNull();
         assertThat(new ReplayableCommand_previous(replayableCommand).act().interactionId()).isEqualTo(previousInteractionId);
@@ -365,7 +378,7 @@ void target_remapping_participant_uses_lookup_and_keeps_unresolved_bookmarks_vis
                 Bookmark.forLogicalTypeNameAndIdentifier("simple.SimpleObject", "1")))
                 .thenReturn(Optional.of(Bookmark.forLogicalTypeNameAndIdentifier("simple.SimpleObject", "2")));
 
-        List<ReplayableCommandParticipant> remappings = replayableCommand(interactionId, commandLogEntry, listener).getParticipants();
+        List<ReplayableCommandParticipant> remappings = replayableCommand(interactionId, commandLogEntry, mock(BookmarkService.class), listener).getParticipants();
 
         assertThat(remappings).hasSize(2);
         ReplayableCommandParticipant participant = remappings.stream()
@@ -395,8 +408,7 @@ void successful_replay_populates_unchanged_actual_bookmarks_and_objects() {
         BookmarkService bookmarkService = mock(BookmarkService.class);
         when(bookmarkService.lookup(targetBookmark)).thenReturn(Optional.of(targetObject));
         when(bookmarkService.lookup(parameterBookmark)).thenReturn(Optional.of(parameterObject));
-        ReplayableCommand replayableCommand = replayableCommand(interactionId, commandLogEntry);
-        replayableCommand.bookmarkService = bookmarkService;
+        ReplayableCommand replayableCommand = replayableCommand(interactionId, commandLogEntry, bookmarkService);
 
         List<ReplayableCommandParticipant> participants = replayableCommand.getParticipants();
 
@@ -421,35 +433,6 @@ void undefined_replay_state_populates_recorded_target_and_argument_bookmarks_and
         assertRecordedTargetAndArgumentAreAvailable(ReplayState.UNDEFINED);
     }
 
-    @Test
-    void exported_replay_state_populates_recorded_target_and_argument_bookmarks_and_objects() {
-        assertRecordedTargetAndArgumentAreAvailable(ReplayState.EXPORTED);
-    }
-
-    @Test
-    void domain_service_target_is_available_in_every_replay_state() {
-        for (ReplayState replayState : ReplayState.values()) {
-            UUID interactionId = UUID.randomUUID();
-            DomainServiceMenu service = new DomainServiceMenu();
-            Bookmark serviceBookmark = Bookmark.forLogicalTypeNameAndIdentifier("demo.DomainServiceMenu", "1");
-            CommandLogEntry commandLogEntry = commandLogEntryWithCommandDto(commandWithTargetOnly(
-                    "demo.DomainServiceMenu", "1"));
-            when(commandLogEntry.getInteractionId()).thenReturn(interactionId);
-            when(commandLogEntry.getReplayState()).thenReturn(replayState);
-            BookmarkService bookmarkService = mock(BookmarkService.class);
-            when(bookmarkService.lookup(serviceBookmark)).thenReturn(Optional.of(service));
-            ReplayableCommand replayableCommand = replayableCommand(interactionId, commandLogEntry);
-            replayableCommand.bookmarkService = bookmarkService;
-
-            ReplayableCommandParticipant targetParticipant = replayableCommand.getParticipants().stream()
-                    .filter(row -> row.getRole() == ReplayableCommandParticipant.Role.TARGET)
-                    .findFirst()
-                    .orElseThrow();
-
-            assertThat(targetParticipant.getActualBookmark()).as(replayState.name()).isEqualTo(serviceBookmark);
-            assertThat(targetParticipant.getTarget()).as(replayState.name()).isSameAs(service);
-        }
-    }
 
     @Test
     void participant_mementos_are_readable_and_do_not_include_bookmarks() {
@@ -459,19 +442,25 @@ void participant_mementos_are_readable_and_do_not_include_bookmarks() {
                 ReplayableCommandParticipant.Role.TARGET,
                 null,
                 Bookmark.forLogicalTypeNameAndIdentifier("demoCustomer", "1"),
-                Bookmark.forLogicalTypeNameAndIdentifier("demoCustomer", "2"));
+                Bookmark.forLogicalTypeNameAndIdentifier("demoCustomer", "2"),
+                null // replayContext
+        );
         ReplayableCommandParticipant parameterParticipant = new ReplayableCommandParticipant(
                 interactionId,
                 ReplayableCommandParticipant.Role.PARAMETER,
                 "customer",
                 Bookmark.forLogicalTypeNameAndIdentifier("demoCustomer", "1"),
-                Bookmark.forLogicalTypeNameAndIdentifier("demoCustomer", "2"));
+                Bookmark.forLogicalTypeNameAndIdentifier("demoCustomer", "2"),
+                null // replayContext
+        );
         ReplayableCommandParticipant resultParticipant = new ReplayableCommandParticipant(
                 interactionId,
                 ReplayableCommandParticipant.Role.RESULT,
                 null,
                 Bookmark.forLogicalTypeNameAndIdentifier("demoInvoice", "1"),
-                Bookmark.forLogicalTypeNameAndIdentifier("demoInvoice", "2"));
+                Bookmark.forLogicalTypeNameAndIdentifier("demoInvoice", "2"),
+                null // replayContext
+        );
 
         assertThat(targetParticipant.viewModelMemento())
                 .isEqualTo("11111111-1111-1111-1111-111111111111--target")
@@ -496,10 +485,10 @@ void participant_rehydrates_derived_bookmarks_from_readable_memento() {
         when(commandLogEntry.getReplayState()).thenReturn(ReplayState.PENDING);
         CommandReplayMappingListener listener = mock(CommandReplayMappingListener.class);
         when(listener.lookup(commandLogEntry, recordedTarget)).thenReturn(Optional.of(actualTarget));
-        ReplayContext replayContext = replayContext(interactionId, commandLogEntry, listener);
+        ReplayContext replayContext = replayContext(interactionId, commandLogEntry, mock(BookmarkService.class), listener);
 
         ReplayableCommandParticipant participant = new ReplayableCommandParticipant(
-                interactionId + "--target", mock(BookmarkService.class), replayContext);
+                interactionId + "--target", replayContext);
 
         assertThat(participant.getRole()).isEqualTo(ReplayableCommandParticipant.Role.TARGET);
         assertThat(participant.getRecordedBookmark()).isEqualTo(recordedTarget);
@@ -518,10 +507,10 @@ void parameter_participant_memento_preserves_parameter_name_containing_delimiter
         CommandLogEntry commandLogEntry = commandLogEntryWithCommandDto(recordedCommandDto);
         when(commandLogEntry.getInteractionId()).thenReturn(interactionId);
         when(commandLogEntry.getReplayState()).thenReturn(ReplayState.OK);
-        ReplayContext replayContext = replayContext(interactionId, commandLogEntry);
+        ReplayContext replayContext = replayContext(interactionId, commandLogEntry, mock(BookmarkService.class));
 
         ReplayableCommandParticipant participant = new ReplayableCommandParticipant(
-                interactionId + "--parameter--" + parameterName, mock(BookmarkService.class), replayContext);
+                interactionId + "--parameter--" + parameterName, replayContext);
 
         assertThat(participant.getParameterName()).isEqualTo(parameterName);
         assertThat(participant.getRecordedBookmark()).isEqualTo(recordedParameter);
@@ -535,7 +524,6 @@ void participant_view_model_constructor_uses_string_memento_first_and_injected_s
         assertThat(constructors).hasSize(1);
         assertThat(constructors[0].getParameterTypes()).containsExactly(
                 String.class,
-                BookmarkService.class,
                 ReplayContext.class);
     }
 
@@ -548,7 +536,7 @@ void pending_replay_leaves_unmapped_target_and_parameter_actual_bookmarks_empty(
         when(commandLogEntry.getInteractionId()).thenReturn(interactionId);
         when(commandLogEntry.getReplayState()).thenReturn(ReplayState.PENDING);
 
-        List<ReplayableCommandParticipant> participants = replayableCommand(interactionId, commandLogEntry).getParticipants();
+        List<ReplayableCommandParticipant> participants = replayableCommand(interactionId, commandLogEntry, mock(BookmarkService.class)).getParticipants();
 
         ReplayableCommandParticipant targetParticipant = participants.stream()
                 .filter(row -> row.getRole() == ReplayableCommandParticipant.Role.TARGET)
@@ -565,16 +553,19 @@ void pending_replay_leaves_unmapped_target_and_parameter_actual_bookmarks_empty(
     @Test
     void participant_resolves_actual_object_best_effort() {
         Object actualObject = new Object();
+        final var bookmarkService = mock(BookmarkService.class);
+        when(bookmarkService.lookup(Bookmark.forLogicalTypeNameAndIdentifier("simple.SimpleObject", "2")))
+                .thenReturn(Optional.of(actualObject));
+        final var replayContext = ReplayContext.builder().bookmarkService(bookmarkService).build();
+
         ReplayableCommandParticipant participant = new ReplayableCommandParticipant(
                 UUID.randomUUID(),
                 ReplayableCommandParticipant.Role.TARGET,
                 null,
                 Bookmark.forLogicalTypeNameAndIdentifier("simple.SimpleObject", "1"),
-                Bookmark.forLogicalTypeNameAndIdentifier("simple.SimpleObject", "2"));
-        BookmarkService bookmarkService = mock(BookmarkService.class);
-        when(bookmarkService.lookup(Bookmark.forLogicalTypeNameAndIdentifier("simple.SimpleObject", "2")))
-                .thenReturn(Optional.of(actualObject));
-        participant.bookmarkService = bookmarkService;
+                Bookmark.forLogicalTypeNameAndIdentifier("simple.SimpleObject", "2"),
+                replayContext
+        );
 
         assertThat(participant.getTarget()).isSameAs(actualObject);
         assertThat(participant.getArgument()).isNull();
@@ -589,7 +580,7 @@ void participant_cosmetics_expose_title_parent_metadata_and_role_specific_visibi
         CommandLogEntry commandLogEntry = commandLogEntryWithCommandDto(recordedCommandDto);
         when(commandLogEntry.getInteractionId()).thenReturn(interactionId);
         when(commandLogEntry.getReplayState()).thenReturn(ReplayState.OK);
-        ReplayableCommand replayableCommand = replayableCommand(interactionId, commandLogEntry);
+        ReplayableCommand replayableCommand = replayableCommand(interactionId, commandLogEntry, mock(BookmarkService.class));
 
         ReplayableCommandParticipant parameterParticipant = replayableCommand.getParticipants().stream()
                 .filter(row -> row.getRole() == ReplayableCommandParticipant.Role.PARAMETER)
@@ -604,7 +595,9 @@ void participant_cosmetics_expose_title_parent_metadata_and_role_specific_visibi
                 ReplayableCommandParticipant.Role.RESULT,
                 null,
                 Bookmark.forLogicalTypeNameAndIdentifier("demoInvoice", "1"),
-                Bookmark.forLogicalTypeNameAndIdentifier("demoInvoice", "2"));
+                Bookmark.forLogicalTypeNameAndIdentifier("demoInvoice", "2"),
+                null    // replayContext
+        );
 
         assertThat(parameterParticipant.title())
                 .contains("Replay participant", "PARAMETER", "simpleObject", "simple.SimpleObject:3");
@@ -654,7 +647,7 @@ void reference_parameter_participant_uses_parameter_name_and_omits_non_reference
                 Bookmark.forLogicalTypeNameAndIdentifier("simple.SimpleObject", "3")))
                 .thenReturn(Optional.of(Bookmark.forLogicalTypeNameAndIdentifier("simple.SimpleObject", "4")));
 
-        List<ReplayableCommandParticipant> remappings = replayableCommand(interactionId, commandLogEntry, listener).getParticipants();
+        List<ReplayableCommandParticipant> remappings = replayableCommand(interactionId, commandLogEntry, mock(BookmarkService.class), listener).getParticipants();
 
         assertThat(remappings).hasSize(2);
         ReplayableCommandParticipant participant = remappings.stream()
@@ -682,8 +675,7 @@ void result_participant_is_only_visible_after_successful_replay() {
         when(listener.lookup(commandLogEntry, recordedResult)).thenReturn(Optional.of(actualResult));
         BookmarkService bookmarkService = mock(BookmarkService.class);
         when(bookmarkService.lookup(actualResult)).thenReturn(Optional.of(actualResultObject));
-        ReplayableCommand replayableCommand = replayableCommand(interactionId, commandLogEntry, listener);
-        replayableCommand.bookmarkService = bookmarkService;
+        ReplayableCommand replayableCommand = replayableCommand(interactionId, commandLogEntry, bookmarkService, listener);
 
         List<ReplayableCommandParticipant> remappings = replayableCommand.getParticipants();
 
@@ -695,7 +687,7 @@ void result_participant_is_only_visible_after_successful_replay() {
         assertThat(participant.getResult()).isSameAs(actualResultObject);
 
         when(commandLogEntry.getReplayState()).thenReturn(ReplayState.PENDING);
-        List<ReplayableCommandParticipant> pendingParticipants = replayableCommand(interactionId, commandLogEntry, listener).getParticipants();
+        List<ReplayableCommandParticipant> pendingParticipants = replayableCommand(interactionId, commandLogEntry, mock(BookmarkService.class), listener).getParticipants();
         assertThat(pendingParticipants).hasSize(1);
         assertThat(pendingParticipants.get(0).getRole()).isEqualTo(ReplayableCommandParticipant.Role.RESULT);
         assertThat(pendingParticipants.get(0).getRecordedBookmark()).isEqualTo(recordedResult);
@@ -712,7 +704,7 @@ void participant_derivation_tolerates_listener_lookup_failure() {
         doThrow(new IllegalStateException("lookup failed"))
                 .when(listener).lookup(commandLogEntry, Bookmark.forLogicalTypeNameAndIdentifier("simple.SimpleObject", "1"));
 
-        List<ReplayableCommandParticipant> participants = replayableCommand(UUID.randomUUID(), commandLogEntry, listener).getParticipants();
+        List<ReplayableCommandParticipant> participants = replayableCommand(UUID.randomUUID(), commandLogEntry, mock(BookmarkService.class), listener).getParticipants();
 
         assertThat(participants).hasSize(2);
         assertThat(participants).allSatisfy(participant -> assertThat(participant.getActualBookmark()).isNull());
@@ -752,8 +744,12 @@ void replay_execution_receives_remapped_command_dto() throws Exception {
                 commandLogEntry,
                 Bookmark.forLogicalTypeNameAndIdentifier("simple.SimpleObject", "3")))
                 .thenReturn(Optional.of(Bookmark.forLogicalTypeNameAndIdentifier("simple.SimpleObject", "4")));
-        ReplayContext replayContext = new ReplayContext(
-                null, null, transactionService, commandLogEntryRepository, commandExecutorService, null, List.of(listener));
+        ReplayContext replayContext = ReplayContext.builder()
+                .transactionService(transactionService)
+                .commandLogEntryRepository(commandLogEntryRepository)
+                .commandExecutorService(commandExecutorService)
+                .commandReplayMappingListener(listener)
+                .build();
 
         new ReplayableCommand(interactionId, replayContext).tryReplayOrRetry();
 
@@ -809,8 +805,12 @@ void replay_failure_preserves_recorded_command_dto_after_input_remapping() throw
                 commandLogEntry,
                 Bookmark.forLogicalTypeNameAndIdentifier("simple.SimpleObject", "3")))
                 .thenReturn(Optional.of(Bookmark.forLogicalTypeNameAndIdentifier("simple.SimpleObject", "4")));
-        ReplayContext replayContext = new ReplayContext(
-                null, null, transactionService, commandLogEntryRepository, commandExecutorService, null, List.of(listener));
+        ReplayContext replayContext = ReplayContext.builder()
+                .transactionService(transactionService)
+                .commandLogEntryRepository(commandLogEntryRepository)
+                .commandExecutorService(commandExecutorService)
+                .commandReplayMappingListener(listener)
+                .build();
 
         new ReplayableCommand(interactionId, replayContext).tryReplayOrRetry();
 
@@ -974,9 +974,12 @@ void notifies_listener_in_same_transaction_as_command_execution() throws Excepti
             assertThat(currentTransaction.get()).isEqualTo(commandExecutionTransaction.get());
             return null;
         }).when(listener).onReplayResult(recordedResult, actualResult, commandLogEntry);
-        ReplayContext replayContext = new ReplayContext(
-                null, null, transactionService, commandLogEntryRepository, commandExecutorService, null, List.of(listener));
-
+        ReplayContext replayContext = ReplayContext.builder()
+                .transactionService(transactionService)
+                .commandLogEntryRepository(commandLogEntryRepository)
+                .commandExecutorService(commandExecutorService)
+                .commandReplayMappingListener(listener)
+                .build();
         Try<ReplayableCommand> result = new ReplayableCommand(interactionId, replayContext).tryReplayOrRetry();
 
         assertThat(result.isSuccess()).isTrue();
@@ -1012,9 +1015,12 @@ void listener_exception_causes_replay_failure() throws Exception {
         CommandReplayMappingListener listener = mock(CommandReplayMappingListener.class);
         doThrow(new IllegalStateException("conflicting result mapping"))
                 .when(listener).onReplayResult(recordedResult, actualResult, commandLogEntry);
-        ReplayContext replayContext = new ReplayContext(
-                null, null, transactionService, commandLogEntryRepository, commandExecutorService, null, List.of(listener));
-
+        ReplayContext replayContext = ReplayContext.builder()
+                .transactionService(transactionService)
+                .commandLogEntryRepository(commandLogEntryRepository)
+                .commandExecutorService(commandExecutorService)
+                .commandReplayMappingListener(listener)
+                .build();
         Try<ReplayableCommand> result = new ReplayableCommand(interactionId, replayContext).tryReplayOrRetry();
 
         assertThat(result.isFailure()).isTrue();
@@ -1070,8 +1076,11 @@ void does_not_notify_listener_when_replay_fails() throws Exception {
                 });
 
         CommandReplayMappingListener listener = mock(CommandReplayMappingListener.class);
-        ReplayContext replayContext = new ReplayContext(
-                null, null, transactionService, commandLogEntryRepository, null, null, List.of(listener));
+        ReplayContext replayContext = ReplayContext.builder()
+                .transactionService(transactionService)
+                .commandLogEntryRepository(commandLogEntryRepository)
+                .commandReplayMappingListener(listener)
+                .build();
 
         new ReplayableCommand(interactionId, replayContext).tryReplayOrRetry();
 
@@ -1092,8 +1101,7 @@ private static void assertRecordedTargetAndArgumentAreAvailable(final ReplayStat
         BookmarkService bookmarkService = mock(BookmarkService.class);
         when(bookmarkService.lookup(targetBookmark)).thenReturn(Optional.of(targetObject));
         when(bookmarkService.lookup(parameterBookmark)).thenReturn(Optional.of(parameterObject));
-        ReplayableCommand replayableCommand = replayableCommand(interactionId, commandLogEntry);
-        replayableCommand.bookmarkService = bookmarkService;
+        ReplayableCommand replayableCommand = replayableCommand(interactionId, commandLogEntry, bookmarkService);
 
         List<ReplayableCommandParticipant> participants = replayableCommand.getParticipants();
 
@@ -1117,29 +1125,36 @@ static class DomainServiceMenu {
     }
 
     private static ReplayableCommand replayableCommand(final CommandReplayMappingListener listener) {
-        ReplayContext replayContext = new ReplayContext(null, null, null, null, null, null, List.of(listener));
+        ReplayContext replayContext = ReplayContext.builder().commandReplayMappingListener(listener).build();
         return new ReplayableCommand(UUID.randomUUID(), replayContext);
     }
 
     private static ReplayableCommand replayableCommand(final CommandLogEntry commandLogEntry) {
-        return replayableCommand(UUID.randomUUID(), commandLogEntry);
+        return replayableCommand(UUID.randomUUID(), commandLogEntry, mock(BookmarkService.class));
     }
 
     private static ReplayableCommand replayableCommand(
             final UUID interactionId,
             final CommandLogEntry commandLogEntry,
+            final BookmarkService bookmarkService,
             final CommandReplayMappingListener... listeners) {
-        return new ReplayableCommand(interactionId, replayContext(interactionId, commandLogEntry, listeners));
+        return new ReplayableCommand(interactionId, replayContext(interactionId, commandLogEntry, bookmarkService, listeners));
     }
 
     private static ReplayContext replayContext(
             final UUID interactionId,
             final CommandLogEntry commandLogEntry,
+            final BookmarkService bookmarkService,
             final CommandReplayMappingListener... listeners) {
         CommandLogEntryRepository commandLogEntryRepository = mock(CommandLogEntryRepository.class);
         when(commandLogEntryRepository.findByInteractionId(interactionId)).thenReturn(Optional.of(commandLogEntry));
-        return new ReplayContext(
-                null, null, null, commandLogEntryRepository, null, null, List.of(listeners));
+        ReplayContext replayContext = ReplayContext.builder()
+                .commandLogEntryRepository(commandLogEntryRepository)
+                .commandReplayMappingListeners(Arrays.asList(listeners))
+                .bookmarkService(bookmarkService)
+                .build();
+
+        return replayContext;
     }
 
     private static CommandLogEntry commandLogEntry(