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`.