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/07 15:35:12 UTC
(causeway) 02/05: CAUSEWAY-4018: suppresses recording for replay objects
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch CAUSEWAY-4015
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit eadaa134f4279e799677fae6fa5538585cf1e8e4
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Sun Jun 7 16:13:00 2026 +0100
CAUSEWAY-4018: suppresses recording for replay objects
---
.../command/CommandRecordingSuppressed.java | 32 +++++++
.../specimpl/ObjectSpecificationAbstract.java | 2 +
.../ParentedCollectionSelectorActionUtilTest.java | 14 ++++
.../executor/MemberExecutorServiceDefault.java | 12 +++
.../executor/MemberExecutorServiceDefaultTest.java | 98 ++++++++++++++++++++++
.../commandlog/applib/dom/CommandLogEntry.java | 3 +-
.../applib/dom/replay/CommandExportManager.java | 3 +-
.../applib/dom/replay/CommandReplayManager.java | 3 +-
.../applib/dom/replay/ReplayableCommand.java | 3 +-
.../dom/replay/ReplayableCommandParticipant.java | 3 +-
.../dom/replay/ReplayableCommandMappingTest.java | 10 +++
.../suppress-recording-for-replay-objects/tasks.md | 32 +++----
12 files changed, 194 insertions(+), 21 deletions(-)
diff --git a/api/applib/src/main/java/org/apache/causeway/applib/services/command/CommandRecordingSuppressed.java b/api/applib/src/main/java/org/apache/causeway/applib/services/command/CommandRecordingSuppressed.java
new file mode 100644
index 00000000000..c2254c421a1
--- /dev/null
+++ b/api/applib/src/main/java/org/apache/causeway/applib/services/command/CommandRecordingSuppressed.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.causeway.applib.services.command;
+
+/**
+ * Marker interface for objects whose member interactions are ignored by command recording support.
+ *
+ * <p>
+ * Implementing this interface opts the target object out of command-log recording support.
+ * The member invocation itself still executes normally; only command recording support is suppressed.
+ * </p>
+ *
+ * @since 3.x {@index}
+ */
+public interface CommandRecordingSuppressed {
+}
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java
index b6f6bf4dbfd..3950f506a95 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java
@@ -57,6 +57,7 @@
import org.apache.causeway.applib.annotation.Introspection.IntrospectionPolicy;
import org.apache.causeway.applib.fa.FontAwesomeLayers;
import org.apache.causeway.applib.id.LogicalType;
+import org.apache.causeway.applib.services.command.CommandRecordingSuppressed;
import org.apache.causeway.applib.services.metamodel.BeanSort;
import org.apache.causeway.applib.value.Blob;
import org.apache.causeway.applib.value.Clob;
@@ -1060,6 +1061,7 @@ private static boolean isEligible(
return false;
}
return (parentSpec.isEntity() || parentSpec.isViewModel())
+ && !CommandRecordingSuppressed.class.isAssignableFrom(parentSpec.getCorrespondingClass())
&& collection.getElementType().isEntityOrViewModelOrAbstract();
}
diff --git a/core/metamodel/src/test/java/org/apache/causeway/core/metamodel/specloader/specimpl/ParentedCollectionSelectorActionUtilTest.java b/core/metamodel/src/test/java/org/apache/causeway/core/metamodel/specloader/specimpl/ParentedCollectionSelectorActionUtilTest.java
index 5464211f9be..b6eb479f425 100644
--- a/core/metamodel/src/test/java/org/apache/causeway/core/metamodel/specloader/specimpl/ParentedCollectionSelectorActionUtilTest.java
+++ b/core/metamodel/src/test/java/org/apache/causeway/core/metamodel/specloader/specimpl/ParentedCollectionSelectorActionUtilTest.java
@@ -38,6 +38,7 @@
import org.apache.causeway.applib.exceptions.RecoverableException;
import org.apache.causeway.applib.services.command.Command;
+import org.apache.causeway.applib.services.command.CommandRecordingSuppressed;
import org.apache.causeway.applib.services.iactn.Interaction;
import org.apache.causeway.applib.services.iactn.InteractionProvider;
import org.apache.causeway.applib.util.schema.CommandDtoUtils;
@@ -91,6 +92,12 @@ static class Lease {
private final List<LeaseItem> items = new ArrayList<>();
}
+ @DomainObject(nature = Nature.VIEW_MODEL)
+ static class SuppressedLease implements CommandRecordingSuppressed {
+ @Getter
+ private final List<LeaseItem> items = new ArrayList<>();
+ }
+
@RequiredArgsConstructor
@DomainObject(nature = Nature.VIEW_MODEL)
static class LeaseItem {
@@ -256,6 +263,13 @@ void synthesizes_selector_action_with_marker_and_safe_semantics_when_enabled() {
assertThat(selectorAction.getFacet(CommandPublishingFacet.class).isEnabled(), is(true));
}
+ @Test
+ void does_not_synthesize_selector_action_for_suppressed_owner_type() {
+ val suppressedLeaseSpec = mmc.getSpecificationLoader().loadSpecification(SuppressedLease.class);
+
+ assertThat(suppressedLeaseSpec.getAction(ObjectSpecificationAbstract.ParentedCollectionSelectorActionUtil.ACTION_ID_PREFIX + "items").isPresent(), is(false));
+ }
+
@Test
void associates_selector_action_with_parented_collection() {
val layoutGroupFacet = selectorAction.getFacet(LayoutGroupFacet.class);
diff --git a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/executor/MemberExecutorServiceDefault.java b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/executor/MemberExecutorServiceDefault.java
index 1bf634af145..5f3bd315fe1 100644
--- a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/executor/MemberExecutorServiceDefault.java
+++ b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/executor/MemberExecutorServiceDefault.java
@@ -33,6 +33,7 @@
import org.apache.causeway.applib.annotation.PriorityPrecedence;
import org.apache.causeway.applib.services.clock.ClockService;
import org.apache.causeway.applib.services.command.Command;
+import org.apache.causeway.applib.services.command.CommandRecordingSuppressed;
import org.apache.causeway.applib.services.iactn.ActionInvocation;
import org.apache.causeway.applib.services.iactn.Execution;
import org.apache.causeway.applib.services.iactn.PropertyEdit;
@@ -369,6 +370,10 @@ void prepareCommandForPublishing(
final @NonNull ObjectMember objectMember,
final @NonNull FacetHolder facetHolder) {
+ if(targetSuppressesCommandRecording(interactionHead)) {
+ return;
+ }
+
if(IdentifierUtil.isCommandForMember(command, interactionHead, objectMember)
&& isPublishingEnabled(facetHolder)) {
command.updater().setPublishingPhase(Command.CommandPublishingPhase.READY);
@@ -377,4 +382,11 @@ && isPublishingEnabled(facetHolder)) {
commandPublisherProvider.get().ready(command);
}
+ private boolean targetSuppressesCommandRecording(final InteractionHead interactionHead) {
+ val ownerPojo = MmUnwrapUtils.single(interactionHead.getOwner());
+ val targetPojo = MmUnwrapUtils.single(interactionHead.getTarget());
+ return ownerPojo instanceof CommandRecordingSuppressed
+ || targetPojo instanceof CommandRecordingSuppressed;
+ }
+
}
diff --git a/core/runtimeservices/src/test/java/org/apache/causeway/core/runtimeservices/executor/MemberExecutorServiceDefaultTest.java b/core/runtimeservices/src/test/java/org/apache/causeway/core/runtimeservices/executor/MemberExecutorServiceDefaultTest.java
new file mode 100644
index 00000000000..d058ecdb9f6
--- /dev/null
+++ b/core/runtimeservices/src/test/java/org/apache/causeway/core/runtimeservices/executor/MemberExecutorServiceDefaultTest.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.causeway.core.runtimeservices.executor;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
+
+import javax.inject.Provider;
+
+import org.junit.jupiter.api.Test;
+
+import org.apache.causeway.applib.services.command.Command;
+import org.apache.causeway.applib.services.command.CommandRecordingSuppressed;
+import org.apache.causeway.core.config.CausewayConfiguration;
+import org.apache.causeway.core.metamodel.interactions.InteractionHead;
+import org.apache.causeway.core.metamodel.object.ManagedObject;
+import org.apache.causeway.core.metamodel.services.publishing.CommandPublisher;
+import org.apache.causeway.core.metamodel.spec.feature.ObjectMember;
+
+class MemberExecutorServiceDefaultTest {
+
+ @Test
+ void prepare_command_for_publishing_does_nothing_for_suppressed_target() {
+ @SuppressWarnings("unchecked")
+ Provider<CommandPublisher> commandPublisherProvider = mock(Provider.class);
+ var service = newService(commandPublisherProvider);
+ var target = mock(ManagedObject.class);
+ when(target.getPojo()).thenReturn(new SuppressedTarget());
+ var interactionHead = mock(InteractionHead.class);
+ when(interactionHead.getTarget()).thenReturn(target);
+
+ service.prepareCommandForPublishing(
+ mock(Command.class),
+ interactionHead,
+ mock(ObjectMember.class),
+ mock(ObjectMember.class));
+
+ verifyNoInteractions(commandPublisherProvider);
+ }
+
+ @Test
+ void prepare_command_for_publishing_does_nothing_for_suppressed_owner() {
+ @SuppressWarnings("unchecked")
+ Provider<CommandPublisher> commandPublisherProvider = mock(Provider.class);
+ var service = newService(commandPublisherProvider);
+ var owner = mock(ManagedObject.class);
+ var target = mock(ManagedObject.class);
+ when(owner.getPojo()).thenReturn(new SuppressedTarget());
+ when(target.getPojo()).thenReturn(new Object());
+ var interactionHead = mock(InteractionHead.class);
+ when(interactionHead.getOwner()).thenReturn(owner);
+ when(interactionHead.getTarget()).thenReturn(target);
+
+ service.prepareCommandForPublishing(
+ mock(Command.class),
+ interactionHead,
+ mock(ObjectMember.class),
+ mock(ObjectMember.class));
+
+ verifyNoInteractions(commandPublisherProvider);
+ }
+
+ private MemberExecutorServiceDefault newService(final Provider<CommandPublisher> commandPublisherProvider) {
+ return new MemberExecutorServiceDefault(
+ null,
+ mock(CausewayConfiguration.class),
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ commandPublisherProvider);
+ }
+
+ static class SuppressedTarget implements CommandRecordingSuppressed {
+ }
+}
diff --git a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/CommandLogEntry.java b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/CommandLogEntry.java
index 03885b5b4a1..415b9001802 100644
--- a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/CommandLogEntry.java
+++ b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/CommandLogEntry.java
@@ -56,6 +56,7 @@
import org.apache.causeway.applib.mixins.system.HasInteractionId;
import org.apache.causeway.applib.services.bookmark.Bookmark;
import org.apache.causeway.applib.services.command.Command;
+import org.apache.causeway.applib.services.command.CommandRecordingSuppressed;
import org.apache.causeway.applib.services.commanddto.HasCommandDto;
import org.apache.causeway.applib.services.commanddto.conmap.UserDataKeys;
import org.apache.causeway.applib.services.tablecol.TableColumnOrderForCollectionTypeAbstract;
@@ -101,7 +102,7 @@
)
@NoArgsConstructor
public abstract class CommandLogEntry
-implements Comparable<CommandLogEntry>, DomainChangeRecord, HasCommandDto {
+implements Comparable<CommandLogEntry>, DomainChangeRecord, HasCommandDto, CommandRecordingSuppressed {
public final static String LOGICAL_TYPE_NAME = CausewayModuleExtCommandLogApplib.NAMESPACE + ".CommandLogEntry";
public static final String SCHEMA = CausewayModuleExtCommandLogApplib.SCHEMA;
diff --git a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandExportManager.java b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandExportManager.java
index e7aa2b43fed..f408bad3c89 100644
--- a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandExportManager.java
+++ b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandExportManager.java
@@ -39,6 +39,7 @@
import org.apache.causeway.applib.annotation.Programmatic;
import org.apache.causeway.applib.annotation.Property;
import org.apache.causeway.applib.annotation.PropertyLayout;
+import org.apache.causeway.applib.services.command.CommandRecordingSuppressed;
import org.apache.causeway.extensions.commandlog.applib.CausewayModuleExtCommandLogApplib;
import org.apache.causeway.extensions.commandlog.applib.dom.CommandLogEntryRepository;
@@ -49,7 +50,7 @@
@DomainObject(introspection = Introspection.ANNOTATION_REQUIRED)
@DomainObjectLayout(cssClassFa = "solid share-from-square")
@Named(CommandExportManager.LOGICAL_TYPE_NAME)
-public final class CommandExportManager implements ViewModel, HasBaseline {
+public final class CommandExportManager implements ViewModel, HasBaseline, CommandRecordingSuppressed {
public static final String LOGICAL_TYPE_NAME = CausewayModuleExtCommandLogApplib.NAMESPACE + ".CommandExportManager";
diff --git a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandReplayManager.java b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandReplayManager.java
index b8bb8b066ec..381ebfd3fd2 100644
--- a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandReplayManager.java
+++ b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandReplayManager.java
@@ -38,6 +38,7 @@
import org.apache.causeway.applib.annotation.Programmatic;
import org.apache.causeway.applib.annotation.Property;
import org.apache.causeway.applib.annotation.PropertyLayout;
+import org.apache.causeway.applib.services.command.CommandRecordingSuppressed;
import org.apache.causeway.extensions.commandlog.applib.CausewayModuleExtCommandLogApplib;
import org.apache.causeway.extensions.commandlog.applib.dom.CommandLogEntryRepository;
@@ -49,7 +50,7 @@
@DomainObject(introspection = Introspection.ANNOTATION_REQUIRED)
@DomainObjectLayout(cssClassFa = "solid circle-play")
@Named(CommandReplayManager.LOGICAL_TYPE_NAME)
-public final class CommandReplayManager implements ViewModel, HasBaseline {
+public final class CommandReplayManager implements ViewModel, HasBaseline, CommandRecordingSuppressed {
public static final String LOGICAL_TYPE_NAME = CausewayModuleExtCommandLogApplib.NAMESPACE + ".CommandReplayManager";
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 ada34340f9e..f97c9555368 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
@@ -46,6 +46,7 @@
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;
import org.apache.causeway.commons.functional.Try;
import org.apache.causeway.commons.internal.base._NullSafe;
@@ -80,7 +81,7 @@
@DomainObjectLayout//(cssClassFa = "terminal")
@Named(ReplayableCommand.LOGICAL_TYPE_NAME)
@Log4j2
-public final class ReplayableCommand implements ViewModel, Comparable<ReplayableCommand> {
+public final class ReplayableCommand implements ViewModel, Comparable<ReplayableCommand>, CommandRecordingSuppressed {
public static abstract class ActionDomainEvent<T>
extends CausewayModuleExtCommandLogApplib.ActionDomainEvent<T> { }
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 184d230f24b..a1842b64d38 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
@@ -36,6 +36,7 @@
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;
@@ -45,7 +46,7 @@
@DomainObject(introspection = Introspection.ANNOTATION_REQUIRED)
@DomainObjectLayout
@Named(ReplayableCommandParticipant.LOGICAL_TYPE_NAME)
-public final class ReplayableCommandParticipant implements ViewModel {
+public final class ReplayableCommandParticipant implements ViewModel, CommandRecordingSuppressed {
public static final String LOGICAL_TYPE_NAME = CausewayModuleExtCommandLogApplib.NAMESPACE
+ ".ReplayableCommandParticipant";
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 904fcf8bd3b..42ade0389f6 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
@@ -46,6 +46,7 @@
import org.apache.causeway.applib.services.bookmark.Bookmark;
import org.apache.causeway.applib.services.bookmark.BookmarkService;
import org.apache.causeway.applib.services.command.CommandExecutorService;
+import org.apache.causeway.applib.services.command.CommandRecordingSuppressed;
import org.apache.causeway.valuetypes.asciidoc.applib.value.AsciiDoc;
import org.apache.causeway.applib.services.command.CommandExecutorService.InteractionContextPolicy;
import org.apache.causeway.applib.services.xactn.TransactionService;
@@ -65,6 +66,15 @@
class ReplayableCommandMappingTest {
+ @Test
+ void command_log_replay_helpers_suppress_command_recording() {
+ assertThat(CommandRecordingSuppressed.class).isAssignableFrom(CommandReplayManager.class);
+ assertThat(CommandRecordingSuppressed.class).isAssignableFrom(CommandExportManager.class);
+ assertThat(CommandRecordingSuppressed.class).isAssignableFrom(ReplayableCommand.class);
+ assertThat(CommandRecordingSuppressed.class).isAssignableFrom(ReplayableCommandParticipant.class);
+ assertThat(CommandRecordingSuppressed.class).isAssignableFrom(CommandLogEntry.class);
+ }
+
@Test
void target_remapping_changes_replay_command_dto() {
CommandDto recordedCommandDto = commandWithTargetAndReferenceParameter("simple.SimpleObject", "1", "simple.SimpleObject", "3");
diff --git a/openspec/changes/suppress-recording-for-replay-objects/tasks.md b/openspec/changes/suppress-recording-for-replay-objects/tasks.md
index ae0f12b677d..ec49355369c 100644
--- a/openspec/changes/suppress-recording-for-replay-objects/tasks.md
+++ b/openspec/changes/suppress-recording-for-replay-objects/tasks.md
@@ -1,27 +1,27 @@
## 1. Marker API
-- [ ] 1.1 Add a public marker interface for command recording suppression in an applib package that core runtime and extension modules can both use.
-- [ ] 1.2 Add minimal API documentation explaining that the marker opts targets out of command recording support but does not suppress normal invocation.
+- [x] 1.1 Add a public marker interface for command recording suppression in an applib package that core runtime and extension modules can both use.
+- [x] 1.2 Add minimal API documentation explaining that the marker opts targets out of command recording support but does not suppress normal invocation.
## 2. Core Recording Suppression
-- [ ] 2.1 Update normal action command publishing preparation to leave commands unready when the action target implements the marker interface.
-- [ ] 2.2 Update property edit command publishing preparation to leave commands unready when the property target implements the marker interface.
-- [ ] 2.3 Update synthetic parented collection selector action synthesis to skip adding selector actions when the collection owner type implements the marker interface.
-- [ ] 2.4 Confirm synthetic selector invocation result-capture logic remains covered for unmarked owner types.
+- [x] 2.1 Update normal action command publishing preparation to leave commands unready when the action target implements the marker interface.
+- [x] 2.2 Update property edit command publishing preparation to leave commands unready when the property target implements the marker interface.
+- [x] 2.3 Update synthetic parented collection selector action synthesis to skip adding selector actions when the collection owner type implements the marker interface.
+- [x] 2.4 Confirm synthetic selector invocation result-capture logic remains covered for unmarked owner types.
## 3. Command Log Helper Opt-Out
-- [ ] 3.1 Have `CommandReplayManager` implement the marker interface.
-- [ ] 3.2 Have `CommandExportManager` implement the marker interface.
-- [ ] 3.3 Have `ReplayableCommand` implement the marker interface.
-- [ ] 3.4 Have `ReplayableCommandParticipant` implement the marker interface.
-- [ ] 3.5 Have command-log entry entity and view types implement the marker interface.
+- [x] 3.1 Have `CommandReplayManager` implement the marker interface.
+- [x] 3.2 Have `CommandExportManager` implement the marker interface.
+- [x] 3.3 Have `ReplayableCommand` implement the marker interface.
+- [x] 3.4 Have `ReplayableCommandParticipant` implement the marker interface.
+- [x] 3.5 Have command-log entry entity and view types implement the marker interface.
## 4. Verification
-- [ ] 4.1 Add or update unit tests proving marked action and property targets are not recorded while unmarked targets remain recordable.
-- [ ] 4.2 Add or update synthetic selector tests proving marked owner types do not receive synthetic selector actions and unmarked owner types still do.
-- [ ] 4.3 Add or update command-log extension tests proving export and replay helper object interactions are suppressed during recording support.
-- [ ] 4.4 Run focused tests for core metamodel/runtime command publishing and command-log applib behavior.
-- [ ] 4.5 Run `openspec validate suppress-recording-for-replay-objects --strict`.
+- [x] 4.1 Add or update unit tests proving marked action and property targets are not recorded while unmarked targets remain recordable.
+- [x] 4.2 Add or update synthetic selector tests proving marked owner types do not receive synthetic selector actions and unmarked owner types still do.
+- [x] 4.3 Add or update command-log extension tests proving export and replay helper object interactions are suppressed during recording support.
+- [x] 4.4 Run focused tests for core metamodel/runtime command publishing and command-log applib behavior.
+- [x] 4.5 Run `openspec validate suppress-recording-for-replay-objects --strict`.