You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by da...@apache.org on 2022/07/13 17:24:34 UTC

[isis] branch ISIS-3002 updated: ISIS-3002: wip ... commandlog test plus Strinifier, mostly

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

danhaywood pushed a commit to branch ISIS-3002
in repository https://gitbox.apache.org/repos/asf/isis.git


The following commit(s) were added to refs/heads/ISIS-3002 by this push:
     new 9d890d1bb7 ISIS-3002: wip ... commandlog test plus Strinifier, mostly
9d890d1bb7 is described below

commit 9d890d1bb731d9c52065c9bcc9bd17948d5d2a6a
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Wed Jul 13 18:24:22 2022 +0100

    ISIS-3002: wip ... commandlog test plus Strinifier, mostly
---
 .../org/apache/isis/applib/IsisModuleApplib.java   |   2 +
 .../applib/mixins/system/DomainChangeRecord.java   |   2 +-
 .../applib/services/bookmark/BookmarkService.java  |  37 +++
 .../isis/commons/internal/memento/_Mementos.java   |   2 +
 .../actions/action/invocation/IdentifierUtil.java  |  45 +++-
 .../publish/command/CommandPublishingFacet.java    |   4 +-
 .../core/metamodel/spec/feature/ObjectAction.java  |   2 -
 .../core/metamodel/spec/feature/ObjectMember.java  |   5 +
 .../specimpl/OneToManyAssociationMixedIn.java      |   7 +-
 .../bookmarks/BookmarkServiceDefault.java          |  33 ++-
 .../command/CommandDtoFactoryDefault.java          |   4 +-
 .../executor/MemberExecutorServiceDefault.java     |   6 +-
 .../images/jpa/JavaAwtBufferedImageJpa.java        |   2 +-
 .../commandlog/applib/dom/CommandLogEntry.java     |  20 +-
 .../applib/dom/CommandLogEntryRepository.java      |  27 +-
 .../subscriber/CommandSubscriberForCommandLog.java |   7 +-
 .../integtest/CommandLogIntegTestAbstract.java     | 256 ++++++++++++++++--
 .../integtest/model/CommandLogTestDomainModel.java |   4 +
 .../commandlog/applib/integtest/model/Counter.java |  53 ++++
 .../applib/integtest/model/CounterRepository.java  |  15 ++
 .../integtest/model/Counter_bumpUsingMixin.java    |  17 ++
 ...umpUsingMixinWithCommandPublishingDisabled.java |  17 ++
 .../jdo/IsisModuleExtCommandLogPersistenceJdo.java |   3 +
 .../commandlog/jdo/dom/CommandLogEntry.java        |  12 +-
 .../commandlog/jdo/CommandLog_IntegTest.java       |  11 +-
 .../extensions/commandlog/jdo/model/Counter.java   |  52 ++++
 .../commandlog/jdo/model/CounterRepository.java    |  36 +++
 .../src/test/resources/application-test.yml        |  29 ++
 .../jpa/IsisModuleExtCommandLogPersistenceJpa.java |   7 +-
 .../commandlog/jpa/dom/CommandLogEntry.java        |  37 +--
 .../commandlog/jpa/dom/CommandLogEntryPK.java      |  58 ++++
 .../jpa/dom/CommandLogEntryRepository.java         |   9 +-
 .../resources/META-INF/orm-commandlog.template     |   6 +-
 .../commandlog/jpa/CommandLog_IntegTest.java       |  12 +-
 .../extensions/commandlog/jpa/model/Counter.java   |  55 ++++
 .../commandlog/jpa/model/CounterRepository.java    |  27 ++
 .../src/test/resources/META-INF/persistence.xml    |  10 +
 .../src/test/resources/application-test.yml        |  29 ++
 extensions/core/commandlog/pom.xml                 |  22 +-
 extensions/pom.xml                                 |  14 +
 .../applib/SessionLogIntegTestAbstract.java        |   4 +-
 .../graphql/viewer/source/gqltestdomain/E1.java    |   2 +
 .../changetracking/EntityChangeTrackerJdo.java     |   4 +-
 .../IsisModulePersistenceJpaIntegration.java       |  27 +-
 .../integration/entity/JpaEntityFacetFactory.java  |  37 ++-
 .../JavaAwtBufferedImageByteArrayConverter.java    |   2 +-
 .../time/JavaTimeIsoOffsetTimeConverter.java}      |   6 +-
 .../time/JavaTimeIsoZonedDateTimeConverter.java}   |   7 +-
 .../util/JavaUtilUuidConverter.java}               |  19 +-
 pom.xml                                            | 291 ++++++++++++++++++++-
 .../testdomain/interact/ActionInteractionTest.java |   9 +-
 51 files changed, 1271 insertions(+), 133 deletions(-)

diff --git a/api/applib/src/main/java/org/apache/isis/applib/IsisModuleApplib.java b/api/applib/src/main/java/org/apache/isis/applib/IsisModuleApplib.java
index 9c48643117..648db58652 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/IsisModuleApplib.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/IsisModuleApplib.java
@@ -37,6 +37,7 @@ import org.apache.isis.applib.services.appfeatui.ApplicationTypeMember;
 import org.apache.isis.applib.services.appfeatui.ApplicationTypeProperty;
 import org.apache.isis.applib.services.bookmark.BookmarkHolder_lookup;
 import org.apache.isis.applib.services.bookmark.BookmarkHolder_object;
+import org.apache.isis.applib.services.bookmark.BookmarkService;
 import org.apache.isis.applib.services.clock.ClockService;
 import org.apache.isis.applib.services.commanddto.conmap.ContentMappingServiceForCommandDto;
 import org.apache.isis.applib.services.commanddto.conmap.ContentMappingServiceForCommandsDto;
@@ -92,6 +93,7 @@ import org.apache.isis.schema.IsisModuleSchema;
     UserMenu.class,
 
     // @Service(s)
+    BookmarkService.Stringifier.Noop.class,
     CommandDtoProcessorServiceIdentity.class,
     ContentMappingServiceForCommandDto.class,
     ContentMappingServiceForCommandsDto.class,
diff --git a/api/applib/src/main/java/org/apache/isis/applib/mixins/system/DomainChangeRecord.java b/api/applib/src/main/java/org/apache/isis/applib/mixins/system/DomainChangeRecord.java
index 72234bd4cd..c32ae57861 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/mixins/system/DomainChangeRecord.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/mixins/system/DomainChangeRecord.java
@@ -245,7 +245,7 @@ public interface DomainChangeRecord extends HasInteractionId, HasUsername, HasTa
     default String getPropertyId() {
         return null;
     }
-    default boolean hidePropertyIds() {
+    default boolean hidePropertyId() {
         return getType() != ChangeType.AUDIT_ENTRY;
     }
 
diff --git a/api/applib/src/main/java/org/apache/isis/applib/services/bookmark/BookmarkService.java b/api/applib/src/main/java/org/apache/isis/applib/services/bookmark/BookmarkService.java
index 4e7c016f59..9daab17065 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/services/bookmark/BookmarkService.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/services/bookmark/BookmarkService.java
@@ -18,9 +18,12 @@
  */
 package org.apache.isis.applib.services.bookmark;
 
+import java.io.Serializable;
 import java.util.Optional;
 
 import org.springframework.lang.Nullable;
+import org.springframework.stereotype.Component;
+import org.springframework.stereotype.Service;
 
 import lombok.NonNull;
 
@@ -91,4 +94,38 @@ public interface BookmarkService {
      */
     Bookmark bookmarkForElseFail(@Nullable Object domainObject);
 
+    /**
+     * SPI to allow other modules to extend the bookmarking mechanism.
+     *
+     * <p>
+     *     Originally introduced to allow JPA CommandLog implementation use CommandLogEntryPK for its primary key.
+     * </p>
+     */
+    interface Stringifier<T> {
+
+        Class<T> handles();
+
+        String stringify(final T value);
+        Object parse(final String stringified);
+
+        @Service
+        public class Noop implements Stringifier<Noop> {
+
+            @Override
+            public Class<Noop> handles() {
+                return Noop.class;
+            }
+
+            @Override
+            public String stringify(Noop value) {
+                throw new IllegalStateException("should never be called");
+            }
+
+            @Override
+            public Object parse(String stringified) {
+                throw new IllegalStateException("should never be called");
+            }
+        }
+
+    }
 }
diff --git a/commons/src/main/java/org/apache/isis/commons/internal/memento/_Mementos.java b/commons/src/main/java/org/apache/isis/commons/internal/memento/_Mementos.java
index ed235b4fff..7426cb3e94 100644
--- a/commons/src/main/java/org/apache/isis/commons/internal/memento/_Mementos.java
+++ b/commons/src/main/java/org/apache/isis/commons/internal/memento/_Mementos.java
@@ -111,6 +111,8 @@ public final class _Mementos {
         public <T> T read(Class<T> cls, Serializable value);
     }
 
+
+
     // -- MEMENTO CONSTRUCTION
 
     /**
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/invocation/IdentifierUtil.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/invocation/IdentifierUtil.java
index b8964823de..59c435700b 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/invocation/IdentifierUtil.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/invocation/IdentifierUtil.java
@@ -26,6 +26,8 @@ import org.apache.isis.applib.services.command.Command;
 import org.apache.isis.commons.internal.base._Refs;
 import org.apache.isis.commons.internal.exceptions._Exceptions;
 import org.apache.isis.core.metamodel.commons.StringExtensions;
+import org.apache.isis.core.metamodel.interactions.InteractionHead;
+import org.apache.isis.core.metamodel.interactions.managed.ActionInteractionHead;
 import org.apache.isis.core.metamodel.spec.ManagedObject;
 import org.apache.isis.core.metamodel.spec.ObjectSpecification;
 import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
@@ -57,21 +59,30 @@ public class IdentifierUtil {
         return objectMember.getFeatureIdentifier().getFullIdentityString();
     }
 
-    public String logicalMemberIdentifierFor(final ObjectMember objectMember) {
+    /**
+     * This assumes that the member is declared, ie is not a mixin.
+     */
+    public String logicalMemberIdentifierForDeclaredMember(final ObjectMember objectMember) {
         if(objectMember instanceof ObjectAction) {
-            return logicalMemberIdentifierFor((ObjectAction)objectMember);
+            return logicalMemberIdentifierForDeclaredMember((ObjectAction)objectMember);
         }
         if(objectMember instanceof OneToOneAssociation) {
-            return logicalMemberIdentifierFor((OneToOneAssociation)objectMember);
+            return logicalMemberIdentifierForDeclaredMember((OneToOneAssociation)objectMember);
         }
         throw new IllegalArgumentException(objectMember.getClass() + " is not supported");
     }
 
-    public String logicalMemberIdentifierFor(final ObjectAction objectAction) {
+    /**
+     * This assumes that the member is declared, ie is not a mixin.
+     */
+    public String logicalMemberIdentifierForDeclaredMember(final ObjectAction objectAction) {
         return logicalMemberIdentifierFor(objectAction.getDeclaringType(), objectAction);
     }
 
-    public String logicalMemberIdentifierFor(final OneToOneAssociation otoa) {
+    /**
+     * This assumes that the member is declared, ie is not a mixin.
+     */
+    public String logicalMemberIdentifierForDeclaredMember(final OneToOneAssociation otoa) {
         return logicalMemberIdentifierFor(otoa.getDeclaringType(), otoa);
     }
 
@@ -109,19 +120,39 @@ public class IdentifierUtil {
     /**
      * Whether given command corresponds to given objectMember.
      * <p>
-     * Is related to {@link #logicalMemberIdentifierFor(ObjectMember)}.
+     * Is related to {@link #logicalMemberIdentifierForDeclaredMember(ObjectMember)}.
      */
     public boolean isCommandForMember(
             final @Nullable Command command,
+            final @NonNull InteractionHead interactionHead,
             final @Nullable ObjectMember objectMember) {
         return command!=null
                 && objectMember!=null
-                && logicalMemberIdentifierFor(objectMember)
+                && logicalMemberIdentifierFor(interactionHead, objectMember)
                     .equals(command.getLogicalMemberIdentifier());
     }
 
     // -- HELPER
 
+    private String logicalMemberIdentifierFor(
+            final @NonNull InteractionHead interactionHead,
+            final ObjectMember objectMember) {
+        if (objectMember instanceof ObjectAction) {
+            ObjectAction objectAction = (ObjectAction) objectMember;
+            if (objectAction.isDeclaredOnMixin()) {
+                if (interactionHead instanceof ActionInteractionHead) {
+                    ObjectAction objectActionOnMixee = ((ActionInteractionHead) interactionHead).getMetaModel();
+                    ObjectSpecification specificationOfMixee = interactionHead.getOwner().getSpecification();
+                    return logicalMemberIdentifierFor(specificationOfMixee, objectActionOnMixee);
+                }
+            }
+            // we fall through to the declared case, which isn't correct; but don't think it matters because
+            // effectively this would be contributed properties or collections, which don't emit commands.
+        }
+
+        return logicalMemberIdentifierForDeclaredMember(objectMember);
+    }
+
     private String logicalMemberIdentifierFor(
             final ObjectSpecification onType, final ObjectMember objectMember) {
         final String logicalTypeName = onType.getLogicalTypeName();
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/members/publish/command/CommandPublishingFacet.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/members/publish/command/CommandPublishingFacet.java
index 4b728f5387..a51d6beac1 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/members/publish/command/CommandPublishingFacet.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/members/publish/command/CommandPublishingFacet.java
@@ -25,6 +25,7 @@ import org.apache.isis.applib.services.publishing.spi.CommandSubscriber;
 import org.apache.isis.core.metamodel.facetapi.Facet;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.facets.actions.action.invocation.IdentifierUtil;
+import org.apache.isis.core.metamodel.interactions.InteractionHead;
 import org.apache.isis.core.metamodel.services.publishing.CommandPublisher;
 import org.apache.isis.core.metamodel.spec.feature.ObjectMember;
 
@@ -56,10 +57,11 @@ public interface CommandPublishingFacet extends Facet {
      */
     public static void prepareCommandForPublishing(
             final @NonNull Command command,
+            final @NonNull InteractionHead interactionHead,
             final @NonNull ObjectMember objectMember,
             final @NonNull FacetHolder facetHolder) {
 
-        if(IdentifierUtil.isCommandForMember(command, objectMember)
+        if(IdentifierUtil.isCommandForMember(command, interactionHead, objectMember)
                 && isPublishingEnabled(facetHolder)) {
             command.updater().setPublishingPhase(CommandPublishingPhase.READY);
         }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectAction.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectAction.java
index 619282844d..2ebcdd703a 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectAction.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectAction.java
@@ -102,8 +102,6 @@ public interface ObjectAction extends ObjectMember {
     /**
      * Invokes the action's method on the target object given the specified set
      * of parameters, checking the visibility, usability and validity first.
-     *
-     * @param mixedInAdapter - will be null for regular actions, and for mixin actions.  When a mixin action invokes its underlying mixedIn action, then will be populated (so that the ActionDomainEvent can correctly provide the underlying mixin)
      */
     ManagedObject executeWithRuleChecking(
             InteractionHead head,
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectMember.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectMember.java
index 7f67e5ff62..4d839d7a5f 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectMember.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectMember.java
@@ -53,6 +53,11 @@ public interface ObjectMember extends ObjectFeature {
     /**
      * Returns the {@link ObjectSpecification} representing the class or interface
      * that declares the member represented by this object.
+     *
+     * <p>
+     *     If the member is a regular member, declared on a class, then this returns that type.
+     *     But if the member is a mixin, then this will return the {@link ObjectSpecification} representing the mixin type.
+     * </p>
      */
     ObjectSpecification getDeclaringType();
 
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToManyAssociationMixedIn.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToManyAssociationMixedIn.java
index 2d3ef6c6ed..a216d1e76e 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToManyAssociationMixedIn.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToManyAssociationMixedIn.java
@@ -20,6 +20,7 @@ package org.apache.isis.core.metamodel.specloader.specimpl;
 
 import org.apache.isis.applib.Identifier;
 import org.apache.isis.applib.annotation.Domain;
+import org.apache.isis.applib.annotation.DomainObject;
 import org.apache.isis.applib.id.LogicalType;
 import org.apache.isis.commons.collections.Can;
 import org.apache.isis.commons.internal.reflection._Annotations;
@@ -46,12 +47,14 @@ extends OneToManyAssociationDefault
 implements MixedInMember {
 
     /**
-     * The type of the mixin (providing the action), eg annotated with {@link org.apache.isis.applib.annotation.Mixin}.
+     * The type of the mixin (providing the action), eg annotated or meta-annotated using
+     * {@link org.apache.isis.applib.annotation.DomainObject} with a {@link DomainObject#nature() nature} of
+     * {@link org.apache.isis.applib.annotation.Nature#MIXIN MIXIN}.
      */
     private final Class<?> mixinType;
 
     /**
-     * The {@link ObjectActionDefault} for the action being mixed in (ie on the {@link #mixinType}.
+     * The {@link ObjectActionDefault} for the action being mixed in (ie on the {@link #mixinType}).
      */
     private final ObjectActionDefault mixinAction;
 
diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/bookmarks/BookmarkServiceDefault.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/bookmarks/BookmarkServiceDefault.java
index 87f69e045b..c7658e6ce3 100644
--- a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/bookmarks/BookmarkServiceDefault.java
+++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/bookmarks/BookmarkServiceDefault.java
@@ -22,6 +22,7 @@ import java.io.Serializable;
 import java.util.List;
 import java.util.Optional;
 import java.util.Set;
+import java.util.UUID;
 
 import javax.annotation.Priority;
 import javax.inject.Inject;
@@ -128,7 +129,8 @@ public class BookmarkServiceDefault implements BookmarkService, SerializingAdapt
     @Override
     public Bookmark bookmarkForElseFail(final @Nullable Object domainObject) {
         return bookmarkFor(domainObject)
-                .orElseThrow(()->_Exceptions.illegalArgument(
+                .orElseThrow(
+                        ()->_Exceptions.illegalArgument(
                         "cannot create bookmark for type %s",
                         domainObject!=null
                             ? specificationLoader.specForType(domainObject.getClass())
@@ -141,6 +143,14 @@ public class BookmarkServiceDefault implements BookmarkService, SerializingAdapt
 
     @Override
     public <T> T read(final Class<T> cls, final Serializable value) {
+        if (stringifiers != null) {
+            for (Stringifier<?> serializer : stringifiers) {
+                if (serializer.handles() == cls) {
+                    return (T) serializer.parse((String)value);
+                }
+            }
+        }
+
 
         if(Bookmark.class.equals(cls)) {
             return _Casts.uncheckedCast(value);
@@ -156,6 +166,14 @@ public class BookmarkServiceDefault implements BookmarkService, SerializingAdapt
 
     @Override
     public Serializable write(final Object value) {
+        if (stringifiers != null) {
+            for (Stringifier stringifier : stringifiers) {
+                if (stringifier.handles() == value.getClass()) {
+                    return stringified(stringifier, value);
+                }
+            }
+        }
+
         if(isPredefinedSerializable(value.getClass())) {
             return (Serializable) value;
         } else {
@@ -163,6 +181,10 @@ public class BookmarkServiceDefault implements BookmarkService, SerializingAdapt
         }
     }
 
+    private static <T> String stringified(Stringifier<T> stringifier, T value) {
+        return stringifier.stringify(value);
+    }
+
     // -- HELPER
 
     private static final Set<Class<? extends Serializable>> serializableFinalTypes = _Sets.of(
@@ -176,7 +198,8 @@ public class BookmarkServiceDefault implements BookmarkService, SerializingAdapt
             Integer[].class, int[].class,
             Long[].class, long[].class,
             Float[].class, float[].class,
-            Double[].class, double[].class
+            Double[].class, double[].class,
+            UUID.class
             );
 
     private static final List<Class<? extends Serializable>> serializableTypes = _Lists.of(
@@ -187,7 +210,7 @@ public class BookmarkServiceDefault implements BookmarkService, SerializingAdapt
             TreeState.class
             );
 
-    private static boolean isPredefinedSerializable(final Class<?> cls) {
+    private boolean isPredefinedSerializable(final Class<?> cls) {
         if(!Serializable.class.isAssignableFrom(cls)) {
             return false;
         }
@@ -205,8 +228,10 @@ public class BookmarkServiceDefault implements BookmarkService, SerializingAdapt
         if(serializableFinalTypes.contains(cls)) {
             return true;
         }
-        return serializableTypes.stream().anyMatch(t->t.isAssignableFrom(cls));
+        return serializableTypes.stream().anyMatch(t -> t.isAssignableFrom(cls));
     }
 
 
+    @Inject List<Stringifier> stringifiers;
+
 }
diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/command/CommandDtoFactoryDefault.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/command/CommandDtoFactoryDefault.java
index a88cd72c00..2517013e4f 100644
--- a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/command/CommandDtoFactoryDefault.java
+++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/command/CommandDtoFactoryDefault.java
@@ -111,7 +111,7 @@ public class CommandDtoFactoryDefault implements CommandDtoFactory {
             final ActionDto actionDto,
             final Can<ManagedObject> argAdapters) {
 
-        actionDto.setLogicalMemberIdentifier(IdentifierUtil.logicalMemberIdentifierFor(objectAction));
+        actionDto.setLogicalMemberIdentifier(IdentifierUtil.logicalMemberIdentifierForDeclaredMember(objectAction));
         actionDto.setMemberIdentifier(IdentifierUtil.memberIdentifierFor(objectAction));
 
         val actionParameters = objectAction.getParameters();
@@ -150,7 +150,7 @@ public class CommandDtoFactoryDefault implements CommandDtoFactory {
             final PropertyDto propertyDto,
             final ManagedObject valueAdapter) {
 
-        propertyDto.setLogicalMemberIdentifier(IdentifierUtil.logicalMemberIdentifierFor(property));
+        propertyDto.setLogicalMemberIdentifier(IdentifierUtil.logicalMemberIdentifierForDeclaredMember(property));
         propertyDto.setMemberIdentifier(IdentifierUtil.memberIdentifierFor(property));
 
         valueMarshaller.recordPropertyValue(propertyDto, property, valueAdapter);
diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/executor/MemberExecutorServiceDefault.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/executor/MemberExecutorServiceDefault.java
index 31b9d9599a..ba1d324562 100644
--- a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/executor/MemberExecutorServiceDefault.java
+++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/executor/MemberExecutorServiceDefault.java
@@ -56,6 +56,7 @@ import org.apache.isis.core.metamodel.facets.members.publish.command.CommandPubl
 import org.apache.isis.core.metamodel.facets.members.publish.execution.ExecutionPublishingFacet;
 import org.apache.isis.core.metamodel.facets.properties.property.modify.PropertySetterOrClearFacetForDomainEventAbstract.EditingVariant;
 import org.apache.isis.core.metamodel.interactions.InteractionHead;
+import org.apache.isis.core.metamodel.interactions.managed.ActionInteractionHead;
 import org.apache.isis.core.metamodel.objectmanager.ObjectManager;
 import org.apache.isis.core.metamodel.objectmanager.ObjectManager.EntityAdaptingMode;
 import org.apache.isis.core.metamodel.services.events.MetamodelEventService;
@@ -64,6 +65,7 @@ import org.apache.isis.core.metamodel.services.publishing.ExecutionPublisher;
 import org.apache.isis.core.metamodel.spec.ManagedObject;
 import org.apache.isis.core.metamodel.spec.ManagedObjects;
 import org.apache.isis.core.metamodel.spec.ManagedObjects.UnwrapUtil;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
 import org.apache.isis.core.metamodel.spec.PackedManagedObject;
 import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
 import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation;
@@ -128,7 +130,7 @@ implements MemberExecutorService {
         val interaction = getInteractionElseFail();
         val command = interaction.getCommand();
 
-        CommandPublishingFacet.prepareCommandForPublishing(command, owningAction, facetHolder);
+        CommandPublishingFacet.prepareCommandForPublishing(command, head, owningAction, facetHolder);
 
         val xrayHandle = _Xray.enterActionInvocation(interactionLayerTracker, interaction, owningAction, head, argumentAdapters);
 
@@ -204,7 +206,7 @@ implements MemberExecutorService {
             return head.getTarget();
         }
 
-        CommandPublishingFacet.prepareCommandForPublishing(command, owningProperty, facetHolder);
+        CommandPublishingFacet.prepareCommandForPublishing(command, head, owningProperty, facetHolder);
 
         val xrayHandle = _Xray.enterPropertyEdit(interactionLayerTracker, interaction, owningProperty, head, newValueAdapter);
 
diff --git a/examples/demo/domain/src/main/java/demoapp/dom/types/javaawt/images/jpa/JavaAwtBufferedImageJpa.java b/examples/demo/domain/src/main/java/demoapp/dom/types/javaawt/images/jpa/JavaAwtBufferedImageJpa.java
index cb4435d27e..2fc1e3727c 100644
--- a/examples/demo/domain/src/main/java/demoapp/dom/types/javaawt/images/jpa/JavaAwtBufferedImageJpa.java
+++ b/examples/demo/domain/src/main/java/demoapp/dom/types/javaawt/images/jpa/JavaAwtBufferedImageJpa.java
@@ -37,7 +37,7 @@ import org.apache.isis.applib.annotation.Optionality;
 import org.apache.isis.applib.annotation.Property;
 import org.apache.isis.applib.annotation.PropertyLayout;
 import org.apache.isis.persistence.jpa.applib.integration.IsisEntityListener;
-import org.apache.isis.persistence.jpa.integration.typeconverters.image.JavaAwtBufferedImageByteArrayConverter;
+import org.apache.isis.persistence.jpa.integration.typeconverters.java.awt.JavaAwtBufferedImageByteArrayConverter;
 
 import lombok.Getter;
 import lombok.NoArgsConstructor;
diff --git a/extensions/core/commandlog/applib/src/main/java/org/apache/isis/extensions/commandlog/applib/dom/CommandLogEntry.java b/extensions/core/commandlog/applib/src/main/java/org/apache/isis/extensions/commandlog/applib/dom/CommandLogEntry.java
index 6060e15910..9db3798356 100644
--- a/extensions/core/commandlog/applib/src/main/java/org/apache/isis/extensions/commandlog/applib/dom/CommandLogEntry.java
+++ b/extensions/core/commandlog/applib/src/main/java/org/apache/isis/extensions/commandlog/applib/dom/CommandLogEntry.java
@@ -87,7 +87,7 @@ import lombok.experimental.UtilityClass;
         editing = Editing.DISABLED
 )
 @DomainObjectLayout(
-        named = "Command",
+        named = "CommandLogEntry",
         titleUiEvent = CommandLogEntry.TitleUiEvent.class,
         iconUiEvent = CommandLogEntry.IconUiEvent.class,
         cssClassUiEvent = CommandLogEntry.CssClassUiEvent.class,
@@ -97,9 +97,9 @@ import lombok.experimental.UtilityClass;
 public abstract class CommandLogEntry
 implements Comparable<CommandLogEntry>, DomainChangeRecord, HasCommandDto {
 
-    public final static String LOGICAL_TYPE_NAME = IsisModuleExtCommandLogApplib.NAMESPACE + ".CommandLog";
+    public final static String LOGICAL_TYPE_NAME = IsisModuleExtCommandLogApplib.NAMESPACE + ".CommandLogEntry";
     public static final String SCHEMA = IsisModuleExtCommandLogApplib.SCHEMA;
-    public static final String TABLE = "Command";
+    public static final String TABLE = "CommandLogEntry";
 
     public static class TitleUiEvent extends IsisModuleExtCommandLogApplib.TitleUiEvent<CommandLogEntry> { }
     public static class IconUiEvent extends IsisModuleExtCommandLogApplib.IconUiEvent<CommandLogEntry> { }
@@ -130,15 +130,12 @@ implements Comparable<CommandLogEntry>, DomainChangeRecord, HasCommandDto {
         public static final String FIND_SINCE = LOGICAL_TYPE_NAME + ".findSince";
         public static final String FIND_MOST_RECENT_REPLAYED = LOGICAL_TYPE_NAME + ".findMostRecentReplayed";
         public static final String FIND_MOST_RECENT_COMPLETED = LOGICAL_TYPE_NAME + ".findMostRecentCompleted";
-        public static final String FIND_NOT_YET_REPLAYED = LOGICAL_TYPE_NAME + ".findNotYetReplayed";
+        public static final String FIND_BY_REPLAY_STATE = LOGICAL_TYPE_NAME + ".findNotYetReplayed";
     }
 
-    /**
-     * Intended for use on primary system.
-     *
-     * @param command
-     */
-    public CommandLogEntry(final Command command) {
+
+    @Programmatic
+    public void init(Command command) {
 
         setInteractionId(command.getInteractionId());
         setUsername(command.getUsername());
@@ -231,6 +228,7 @@ implements Comparable<CommandLogEntry>, DomainChangeRecord, HasCommandDto {
     @Retention(RetentionPolicy.RUNTIME)
     public @interface InteractionId {
         class DomainEvent extends PropertyDomainEvent<UUID> {}
+        String NAME = "interactionId";
         int MAX_LENGTH = HasInteractionId.InteractionId.MAX_LENGTH;
         boolean NULLABLE = HasInteractionId.InteractionId.NULLABLE;
         String ALLOWS_NULL = HasInteractionId.InteractionId.ALLOWS_NULL;
@@ -311,7 +309,7 @@ implements Comparable<CommandLogEntry>, DomainChangeRecord, HasCommandDto {
     @Retention(RetentionPolicy.RUNTIME)
     public @interface Parent {
         class DomainEvent extends PropertyDomainEvent<CommandLogEntry> {}
-        String NAME = "parentId";
+        String NAME = "parentInteractionId";
         boolean NULLABLE = true;
         String ALLOWS_NULL = "true";
     }
diff --git a/extensions/core/commandlog/applib/src/main/java/org/apache/isis/extensions/commandlog/applib/dom/CommandLogEntryRepository.java b/extensions/core/commandlog/applib/src/main/java/org/apache/isis/extensions/commandlog/applib/dom/CommandLogEntryRepository.java
index 47b032b551..8fac17c4d0 100644
--- a/extensions/core/commandlog/applib/src/main/java/org/apache/isis/extensions/commandlog/applib/dom/CommandLogEntryRepository.java
+++ b/extensions/core/commandlog/applib/src/main/java/org/apache/isis/extensions/commandlog/applib/dom/CommandLogEntryRepository.java
@@ -42,6 +42,7 @@ import org.apache.isis.applib.services.command.Command;
 import org.apache.isis.applib.services.factory.FactoryService;
 import org.apache.isis.applib.services.repository.RepositoryService;
 import org.apache.isis.applib.util.schema.CommandDtoUtils;
+import org.apache.isis.core.config.environment.IsisSystemEnvironment;
 import org.apache.isis.schema.cmd.v2.CommandDto;
 import org.apache.isis.schema.cmd.v2.CommandsDto;
 import org.apache.isis.schema.cmd.v2.MapDto;
@@ -52,6 +53,7 @@ import lombok.val;
 
 public abstract class CommandLogEntryRepository<C extends CommandLogEntry> {
 
+
     public static class NotFoundException extends RecoverableException {
         private static final long serialVersionUID = 1L;
         @Getter
@@ -64,6 +66,7 @@ public abstract class CommandLogEntryRepository<C extends CommandLogEntry> {
 
     @Inject Provider<RepositoryService> repositoryServiceProvider;
     @Inject FactoryService factoryService;
+    @Inject IsisSystemEnvironment isisSystemEnvironment;
 
     private final Class<C> commandLogEntryClass;
 
@@ -73,7 +76,7 @@ public abstract class CommandLogEntryRepository<C extends CommandLogEntry> {
 
     public C createEntryAndPersist(final Command command, CommandLogEntry parentEntryIfAny) {
         C c = factoryService.detachedEntity(commandLogEntryClass);
-        c.setCommandDto(command.getCommandDto());
+        c.init(command);
         c.setParent(parentEntryIfAny);
         persist(c);
         return c;
@@ -82,7 +85,14 @@ public abstract class CommandLogEntryRepository<C extends CommandLogEntry> {
     public Optional<C> findByInteractionId(final UUID interactionId) {
         return repositoryService().firstMatch(
                 Query.named(commandLogEntryClass,  CommandLogEntry.Nq.FIND_BY_INTERACTION_ID)
-                        .withParameter("interactionId", interactionId));
+                        .withParameter("interactionId", convert(interactionId)));
+    }
+
+    /**
+     * optional hook
+     */
+    protected Object convert(UUID interactionId) {
+        return interactionId;
     }
 
     public List<C> findByParent(final CommandLogEntry parent) {
@@ -258,7 +268,9 @@ public abstract class CommandLogEntryRepository<C extends CommandLogEntry> {
 
     public List<C> findNotYetReplayed() {
         return repositoryService().allMatches(
-                Query.named(commandLogEntryClass, CommandLogEntry.Nq.FIND_NOT_YET_REPLAYED).withLimit(10));
+                Query.named(commandLogEntryClass, CommandLogEntry.Nq.FIND_BY_REPLAY_STATE)
+                        .withParameter("replayState", ReplayState.PENDING)
+                        .withLimit(10));
     }
 
 
@@ -371,6 +383,15 @@ public abstract class CommandLogEntryRepository<C extends CommandLogEntry> {
                 : null;
     }
 
+    /**
+     * for testing purposes only
+     */
+    public void removeAll() {
+        if (isisSystemEnvironment.getDeploymentType().isProduction()) {
+            throw new IllegalStateException("Cannot removeAll in production systems");
+        }
+        repositoryService().removeAll(commandLogEntryClass);
+    }
 
 
 }
diff --git a/extensions/core/commandlog/applib/src/main/java/org/apache/isis/extensions/commandlog/applib/subscriber/CommandSubscriberForCommandLog.java b/extensions/core/commandlog/applib/src/main/java/org/apache/isis/extensions/commandlog/applib/subscriber/CommandSubscriberForCommandLog.java
index 3344bdc1cd..356df758ff 100644
--- a/extensions/core/commandlog/applib/src/main/java/org/apache/isis/extensions/commandlog/applib/subscriber/CommandSubscriberForCommandLog.java
+++ b/extensions/core/commandlog/applib/src/main/java/org/apache/isis/extensions/commandlog/applib/subscriber/CommandSubscriberForCommandLog.java
@@ -51,9 +51,10 @@ public class CommandSubscriberForCommandLog implements CommandSubscriber {
     @Override
     public void onCompleted(final Command command) {
 
-        if(!command.isSystemStateChanged()) {
-            return;
-        }
+        // TODO: JPA does not yet support lifecycle listeners, so always would be false.
+//        if(!command.isSystemStateChanged()) {
+//            return;
+//        }
 
         val existingCommandJdoIfAny =
                 commandLogEntryRepository.findByInteractionId(command.getInteractionId());
diff --git a/extensions/core/commandlog/applib/src/test/java/org/apache/isis/extensions/commandlog/applib/integtest/CommandLogIntegTestAbstract.java b/extensions/core/commandlog/applib/src/test/java/org/apache/isis/extensions/commandlog/applib/integtest/CommandLogIntegTestAbstract.java
index 9287dd3eed..e6c9195dab 100644
--- a/extensions/core/commandlog/applib/src/test/java/org/apache/isis/extensions/commandlog/applib/integtest/CommandLogIntegTestAbstract.java
+++ b/extensions/core/commandlog/applib/src/test/java/org/apache/isis/extensions/commandlog/applib/integtest/CommandLogIntegTestAbstract.java
@@ -18,53 +18,271 @@
  */
 package org.apache.isis.extensions.commandlog.applib.integtest;
 
-import java.sql.Timestamp;
-import java.time.Duration;
-import java.time.Instant;
-import java.util.Date;
 import java.util.List;
 import java.util.Optional;
 import java.util.UUID;
-import java.util.concurrent.atomic.AtomicInteger;
 
 import javax.inject.Inject;
 
-import org.assertj.core.api.Assertions;
-import org.eclipse.persistence.logging.SessionLogEntry;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
-import org.springframework.beans.factory.annotation.Qualifier;
 
-import org.apache.isis.applib.annotation.Value;
-import org.apache.isis.applib.services.session.SessionLogService;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.apache.isis.applib.mixins.system.DomainChangeRecord;
+import org.apache.isis.applib.services.bookmark.Bookmark;
+import org.apache.isis.applib.services.bookmark.BookmarkService;
+import org.apache.isis.applib.services.iactnlayer.InteractionService;
+import org.apache.isis.applib.services.metamodel.MetaModelService;
 import org.apache.isis.applib.services.wrapper.WrapperFactory;
-import org.apache.isis.extensions.commandlog.applib.app.CommandLogMenu;
+import org.apache.isis.core.config.presets.IsisPresets;
 import org.apache.isis.extensions.commandlog.applib.dom.CommandLogEntry;
 import org.apache.isis.extensions.commandlog.applib.dom.CommandLogEntryRepository;
+import org.apache.isis.extensions.commandlog.applib.dom.ReplayState;
+import org.apache.isis.extensions.commandlog.applib.integtest.model.Counter;
+import org.apache.isis.extensions.commandlog.applib.integtest.model.CounterRepository;
+import org.apache.isis.extensions.commandlog.applib.integtest.model.Counter_bumpUsingMixin;
+import org.apache.isis.extensions.commandlog.applib.integtest.model.Counter_bumpUsingMixinWithCommandPublishingDisabled;
+import org.apache.isis.schema.cmd.v2.ActionDto;
+import org.apache.isis.schema.cmd.v2.CommandDto;
+import org.apache.isis.schema.cmd.v2.PropertyDto;
 import org.apache.isis.testing.integtestsupport.applib.IsisIntegrationTestAbstract;
 
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
+import lombok.val;
 
 public abstract class CommandLogIntegTestAbstract extends IsisIntegrationTestAbstract {
 
+
+    @BeforeAll
+    static void beforeAll() {
+        IsisPresets.forcePrototyping();
+    }
+
+    Counter counter;
+
     @BeforeEach
-    void setUp() {
+    void beforeEach() {
+
+        counter = createE1();
+
+        Optional<? extends CommandLogEntry> mostRecentCompleted = commandLogEntryRepository.findMostRecentCompleted();
+        assertThat(mostRecentCompleted).isEmpty();
+    }
+
+    private Counter createE1() {
+        List<Counter> before = counterRepository.find();
+        assertThat(before).isEmpty();
+
+        val e1 = counterRepository.persist(newCounter());
+
+        List<Counter> after = counterRepository.find();
+        assertThat(after).hasSize(1);
+
+        return e1;
+    }
+
+    protected abstract Counter newCounter();
+
+    @AfterEach
+    void afterEach() {
+        counterRepository.remove(counter);
+        commandLogEntryRepository.removeAll();
     }
 
     @Test
     void invoke_mixin() {
 
-        List<CommandLogEntry> notYetReplayed = commandLogEntryRepository.findNotYetReplayed();
+        // when
+        wrapperFactory.wrapMixin(Counter_bumpUsingMixin.class, counter).act();
+        interactionService.closeInteractionLayers();    // to flush
+
+        interactionService.openInteraction();
+
+        // then
+        Optional<? extends CommandLogEntry> mostRecentCompleted = commandLogEntryRepository.findMostRecentCompleted();
+        assertThat(mostRecentCompleted).isPresent();
+
+        CommandLogEntry commandLogEntry = mostRecentCompleted.get();
+
+        assertThat(commandLogEntry.getInteractionId()).isNotNull();
+        assertThat(commandLogEntry.getCompletedAt()).isNotNull();
+        assertThat(commandLogEntry.getDuration()).isNotNull();
+        assertThat(commandLogEntry.getException()).isEqualTo("");
+        assertThat(commandLogEntry.getLogicalMemberIdentifier()).isNotNull();
+        assertThat(commandLogEntry.getLogicalMemberIdentifier()).isEqualTo("commandlog.test.Counter#bumpUsingMixin");
+        assertThat(commandLogEntry.getUsername()).isEqualTo("__system");
+        assertThat(commandLogEntry.getResult()).isNotNull();
+        assertThat(commandLogEntry.getResultSummary()).isEqualTo("OK");
+        assertThat(commandLogEntry.getReplayState()).isEqualTo(ReplayState.UNDEFINED);
+        assertThat(commandLogEntry.getReplayStateFailureReason()).isNull();
+        assertThat(commandLogEntry.getTarget()).isNotNull();
+        assertThat(commandLogEntry.getTimestamp()).isNotNull();
+        assertThat(commandLogEntry.getType()).isEqualTo(DomainChangeRecord.ChangeType.COMMAND);
+        assertThat(commandLogEntry.getCommandDto()).isNotNull();
+        CommandDto commandDto = commandLogEntry.getCommandDto();
+        assertThat(commandDto).isNotNull();
+        assertThat(commandDto.getMember()).isInstanceOf(ActionDto.class);
+        assertThat(commandDto.getMember().getLogicalMemberIdentifier()).isEqualTo(commandLogEntry.getLogicalMemberIdentifier());
+    }
+
+    @Test
+    void invoke_direct() {
+
+        // when
+        wrapperFactory.wrap(counter).bumpUsingDeclaredAction();
+        interactionService.closeInteractionLayers();    // to flush
+
+        interactionService.openInteraction();
+
+        // then
+        Optional<? extends CommandLogEntry> mostRecentCompleted = commandLogEntryRepository.findMostRecentCompleted();
+        assertThat(mostRecentCompleted).isPresent();
+
+        CommandLogEntry commandLogEntry = mostRecentCompleted.get();
+
+        assertThat(commandLogEntry.getInteractionId()).isNotNull();
+        assertThat(commandLogEntry.getCompletedAt()).isNotNull();
+        assertThat(commandLogEntry.getDuration()).isNotNull();
+        assertThat(commandLogEntry.getException()).isEqualTo("");
+        assertThat(commandLogEntry.getLogicalMemberIdentifier()).isNotNull();
+        assertThat(commandLogEntry.getLogicalMemberIdentifier()).isEqualTo("commandlog.test.Counter#bumpUsingDeclaredAction");
+        assertThat(commandLogEntry.getUsername()).isEqualTo("__system");
+        assertThat(commandLogEntry.getResult()).isNotNull();
+        assertThat(commandLogEntry.getResultSummary()).isEqualTo("OK");
+        assertThat(commandLogEntry.getReplayState()).isEqualTo(ReplayState.UNDEFINED);
+        assertThat(commandLogEntry.getReplayStateFailureReason()).isNull();
+        assertThat(commandLogEntry.getTarget()).isNotNull();
+        assertThat(commandLogEntry.getTimestamp()).isNotNull();
+        assertThat(commandLogEntry.getType()).isEqualTo(DomainChangeRecord.ChangeType.COMMAND);
+        assertThat(commandLogEntry.getCommandDto()).isNotNull();
+        CommandDto commandDto = commandLogEntry.getCommandDto();
+        assertThat(commandDto).isNotNull();
+        assertThat(commandDto.getMember()).isInstanceOf(ActionDto.class);
+        assertThat(commandDto.getMember().getLogicalMemberIdentifier()).isEqualTo(commandLogEntry.getLogicalMemberIdentifier());
+    }
+
+    @Test
+    void invoke_mixin_disabled() {
+
+        // when
+        wrapperFactory.wrapMixin(Counter_bumpUsingMixinWithCommandPublishingDisabled.class, counter).act();
+        interactionService.closeInteractionLayers();    // to flush
+
+        interactionService.openInteraction();
+
+        // then
+        Optional<? extends CommandLogEntry> mostRecentCompleted = commandLogEntryRepository.findMostRecentCompleted();
+        assertThat(mostRecentCompleted).isEmpty();
+    }
+
+    @Test
+    void invoke_direct_disabled() {
+
+        // when
+        wrapperFactory.wrap(counter).bumpUsingDeclaredActionWithCommandPublishingDisabled();
+        interactionService.closeInteractionLayers();    // to flush
+
+        interactionService.openInteraction();
+
+        // then
+        Optional<? extends CommandLogEntry> mostRecentCompleted = commandLogEntryRepository.findMostRecentCompleted();
+        assertThat(mostRecentCompleted).isEmpty();
+    }
+
+
+
+    @Test
+    void edit() {
+
+        // when
+        wrapperFactory.wrap(counter).setNum(99L);
+        interactionService.closeInteractionLayers();    // to flush
+
+        interactionService.openInteraction();
+
+        // then
+        Optional<? extends CommandLogEntry> mostRecentCompleted = commandLogEntryRepository.findMostRecentCompleted();
+        assertThat(mostRecentCompleted).isPresent();
+
+        CommandLogEntry commandLogEntry = mostRecentCompleted.get();
+
+        assertThat(commandLogEntry.getInteractionId()).isNotNull();
+        assertThat(commandLogEntry.getCompletedAt()).isNotNull();
+        assertThat(commandLogEntry.getDuration()).isNotNull();
+        assertThat(commandLogEntry.getException()).isEqualTo("");
+        assertThat(commandLogEntry.getLogicalMemberIdentifier()).isNotNull();
+        assertThat(commandLogEntry.getLogicalMemberIdentifier()).isEqualTo("commandlog.test.Counter#num");
+        assertThat(commandLogEntry.getUsername()).isEqualTo("__system");
+        assertThat(commandLogEntry.getResult()).isNull();
+        assertThat(commandLogEntry.getResultSummary()).isEqualTo("OK (VOID)");
+        assertThat(commandLogEntry.getReplayState()).isEqualTo(ReplayState.UNDEFINED);
+        assertThat(commandLogEntry.getReplayStateFailureReason()).isNull();
+        assertThat(commandLogEntry.getTarget()).isNotNull();
+        assertThat(commandLogEntry.getTimestamp()).isNotNull();
+        assertThat(commandLogEntry.getType()).isEqualTo(DomainChangeRecord.ChangeType.COMMAND);
+        CommandDto commandDto = commandLogEntry.getCommandDto();
+        assertThat(commandDto).isNotNull();
+        assertThat(commandDto.getMember()).isInstanceOf(PropertyDto.class);
+    }
+
+    @Test
+    void edit_disabled() {
+
+        // when
+        wrapperFactory.wrap(counter).setNum2(99L);
+        interactionService.closeInteractionLayers();    // to flush
+
+        interactionService.openInteraction();
+
+        // then
+        Optional<? extends CommandLogEntry> mostRecentCompleted = commandLogEntryRepository.findMostRecentCompleted();
+        assertThat(mostRecentCompleted).isEmpty();
+    }
+
+
+    @Test
+    void roundtrip_CLE_bookmarks() {
+
+        // given
+        wrapperFactory.wrapMixin(Counter_bumpUsingMixin.class, counter).act();
+        interactionService.closeInteractionLayers();    // to flush
+
+        interactionService.openInteraction();
+        Optional<? extends CommandLogEntry> mostRecentCompleted = commandLogEntryRepository.findMostRecentCompleted();
+
+        CommandLogEntry commandLogEntry = mostRecentCompleted.get();
+        CommandDto commandDto = commandLogEntry.getCommandDto();
+
+        // when
+        Optional<Bookmark> cleBookmarkIfAny = bookmarkService.bookmarkFor(commandLogEntry);
+
+        // then
+        assertThat(cleBookmarkIfAny).isPresent();
+        Bookmark cleBookmark = cleBookmarkIfAny.get();
+        UUID.fromString(cleBookmark.getIdentifier()); // should not fail, ie check the format is as we expect
+
+        // when we start a new session and lookup from the bookmark
+        interactionService.closeInteractionLayers();
+        interactionService.openInteraction();
+
+        Optional<Object> cle2IfAny = bookmarkService.lookup(cleBookmarkIfAny.get());
+        assertThat(cle2IfAny).isPresent();
+
+        CommandLogEntry cle2 = (CommandLogEntry) cle2IfAny.get();
+        CommandDto commandDto2 = cle2.getCommandDto();
 
-        wrapperFactory.wrapMixin(CommandLogMenu.truncateLog.class, commandLogMenu).act();
+        assertThat(commandDto2).isEqualTo(commandDto);
 
-        List<CommandLogEntry> notYetReplayedAfter = commandLogEntryRepository.findNotYetReplayed();
 
     }
 
-    @Inject CommandLogMenu commandLogMenu;
-    @Inject CommandLogEntryRepository<CommandLogEntry> commandLogEntryRepository;
+    @Inject CommandLogEntryRepository<? extends CommandLogEntry> commandLogEntryRepository;
+    @Inject InteractionService interactionService;
+    @Inject CounterRepository counterRepository;
     @Inject WrapperFactory wrapperFactory;
+    @Inject BookmarkService bookmarkService;
+    @Inject MetaModelService metaModelService;
 
 }
diff --git a/extensions/core/commandlog/applib/src/test/java/org/apache/isis/extensions/commandlog/applib/integtest/model/CommandLogTestDomainModel.java b/extensions/core/commandlog/applib/src/test/java/org/apache/isis/extensions/commandlog/applib/integtest/model/CommandLogTestDomainModel.java
new file mode 100644
index 0000000000..a1fae7ec42
--- /dev/null
+++ b/extensions/core/commandlog/applib/src/test/java/org/apache/isis/extensions/commandlog/applib/integtest/model/CommandLogTestDomainModel.java
@@ -0,0 +1,4 @@
+package org.apache.isis.extensions.commandlog.applib.integtest.model;
+
+public class CommandLogTestDomainModel {
+}
diff --git a/extensions/core/commandlog/applib/src/test/java/org/apache/isis/extensions/commandlog/applib/integtest/model/Counter.java b/extensions/core/commandlog/applib/src/test/java/org/apache/isis/extensions/commandlog/applib/integtest/model/Counter.java
new file mode 100644
index 0000000000..71d445c456
--- /dev/null
+++ b/extensions/core/commandlog/applib/src/test/java/org/apache/isis/extensions/commandlog/applib/integtest/model/Counter.java
@@ -0,0 +1,53 @@
+package org.apache.isis.extensions.commandlog.applib.integtest.model;
+
+import javax.inject.Named;
+
+import org.apache.isis.applib.annotation.Action;
+import org.apache.isis.applib.annotation.DomainObject;
+import org.apache.isis.applib.annotation.Editing;
+import org.apache.isis.applib.annotation.Nature;
+import org.apache.isis.applib.annotation.Property;
+import org.apache.isis.applib.annotation.Publishing;
+
+@Named("commandlog.test.Counter")
+@DomainObject(nature = Nature.ENTITY)
+public abstract class Counter implements Comparable<Counter> {
+
+    public abstract Long getId();
+    public abstract void setId(Long id);
+
+    @Property(editing = Editing.ENABLED, commandPublishing = Publishing.ENABLED)
+    public abstract Long getNum();
+    public abstract void setNum(Long num);
+
+    @Property(editing = Editing.ENABLED, commandPublishing = Publishing.DISABLED)
+    public abstract Long getNum2();
+    public abstract void setNum2(Long num2);
+
+    public abstract String getName();
+    public abstract void setName(String name);
+
+    @Action(commandPublishing = Publishing.ENABLED)
+    public Counter bumpUsingDeclaredAction() {
+        return doBump();
+    }
+
+    @Action(commandPublishing = Publishing.DISABLED)
+    public Counter bumpUsingDeclaredActionWithCommandPublishingDisabled() {
+        return doBump();
+    }
+
+    Counter doBump() {
+        if (getNum() == null) {
+            setNum(1L);
+        } else {
+            setNum(getNum() + 1);
+        }
+        return this;
+    }
+
+    @Override
+    public int compareTo(final Counter o) {
+        return this.getName().compareTo(o.getName());
+    }
+}
diff --git a/extensions/core/commandlog/applib/src/test/java/org/apache/isis/extensions/commandlog/applib/integtest/model/CounterRepository.java b/extensions/core/commandlog/applib/src/test/java/org/apache/isis/extensions/commandlog/applib/integtest/model/CounterRepository.java
new file mode 100644
index 0000000000..08bb352664
--- /dev/null
+++ b/extensions/core/commandlog/applib/src/test/java/org/apache/isis/extensions/commandlog/applib/integtest/model/CounterRepository.java
@@ -0,0 +1,15 @@
+package org.apache.isis.extensions.commandlog.applib.integtest.model;
+
+import java.util.List;
+
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface CounterRepository<X extends Counter> {
+
+    List<X> find();
+
+    X persist(X e1);
+
+    void remove(X e1);
+}
diff --git a/extensions/core/commandlog/applib/src/test/java/org/apache/isis/extensions/commandlog/applib/integtest/model/Counter_bumpUsingMixin.java b/extensions/core/commandlog/applib/src/test/java/org/apache/isis/extensions/commandlog/applib/integtest/model/Counter_bumpUsingMixin.java
new file mode 100644
index 0000000000..c07848137f
--- /dev/null
+++ b/extensions/core/commandlog/applib/src/test/java/org/apache/isis/extensions/commandlog/applib/integtest/model/Counter_bumpUsingMixin.java
@@ -0,0 +1,17 @@
+package org.apache.isis.extensions.commandlog.applib.integtest.model;
+
+import org.apache.isis.applib.annotation.Action;
+import org.apache.isis.applib.annotation.Publishing;
+
+import lombok.RequiredArgsConstructor;
+
+@Action(commandPublishing = Publishing.ENABLED)
+@RequiredArgsConstructor
+public class Counter_bumpUsingMixin {
+
+    private final Counter counter;
+
+    public Counter act() {
+        return counter.doBump();
+    }
+}
diff --git a/extensions/core/commandlog/applib/src/test/java/org/apache/isis/extensions/commandlog/applib/integtest/model/Counter_bumpUsingMixinWithCommandPublishingDisabled.java b/extensions/core/commandlog/applib/src/test/java/org/apache/isis/extensions/commandlog/applib/integtest/model/Counter_bumpUsingMixinWithCommandPublishingDisabled.java
new file mode 100644
index 0000000000..d6e16d093d
--- /dev/null
+++ b/extensions/core/commandlog/applib/src/test/java/org/apache/isis/extensions/commandlog/applib/integtest/model/Counter_bumpUsingMixinWithCommandPublishingDisabled.java
@@ -0,0 +1,17 @@
+package org.apache.isis.extensions.commandlog.applib.integtest.model;
+
+import org.apache.isis.applib.annotation.Action;
+import org.apache.isis.applib.annotation.Publishing;
+
+import lombok.RequiredArgsConstructor;
+
+@Action(commandPublishing = Publishing.DISABLED)
+@RequiredArgsConstructor
+public class Counter_bumpUsingMixinWithCommandPublishingDisabled {
+
+    private final Counter counter;
+
+    public Counter act() {
+        return counter.doBump();
+    }
+}
diff --git a/extensions/core/commandlog/persistence-jdo/src/main/java/org/apache/isis/extensions/commandlog/jdo/IsisModuleExtCommandLogPersistenceJdo.java b/extensions/core/commandlog/persistence-jdo/src/main/java/org/apache/isis/extensions/commandlog/jdo/IsisModuleExtCommandLogPersistenceJdo.java
index fd8d6eb703..933d95be0c 100644
--- a/extensions/core/commandlog/persistence-jdo/src/main/java/org/apache/isis/extensions/commandlog/jdo/IsisModuleExtCommandLogPersistenceJdo.java
+++ b/extensions/core/commandlog/persistence-jdo/src/main/java/org/apache/isis/extensions/commandlog/jdo/IsisModuleExtCommandLogPersistenceJdo.java
@@ -24,6 +24,7 @@ import org.springframework.context.annotation.Import;
 import org.apache.isis.extensions.commandlog.applib.IsisModuleExtCommandLogApplib;
 import org.apache.isis.extensions.commandlog.jdo.dom.CommandLogEntry;
 import org.apache.isis.extensions.commandlog.jdo.dom.CommandLogEntryRepository;
+import org.apache.isis.persistence.jdo.datanucleus.IsisModulePersistenceJdoDatanucleus;
 import org.apache.isis.testing.fixtures.applib.fixturescripts.FixtureScript;
 import org.apache.isis.testing.fixtures.applib.teardown.jdo.TeardownFixtureJdoAbstract;
 
@@ -33,7 +34,9 @@ import org.apache.isis.testing.fixtures.applib.teardown.jdo.TeardownFixtureJdoAb
 @Configuration
 @Import({
         // modules
+        // IsisModuleTestingFixturesApplib.class,
         IsisModuleExtCommandLogApplib.class,
+        IsisModulePersistenceJdoDatanucleus.class,
 
         // @Service's
         CommandLogEntryRepository.class,
diff --git a/extensions/core/commandlog/persistence-jdo/src/main/java/org/apache/isis/extensions/commandlog/jdo/dom/CommandLogEntry.java b/extensions/core/commandlog/persistence-jdo/src/main/java/org/apache/isis/extensions/commandlog/jdo/dom/CommandLogEntry.java
index 52b444fbb7..156ad180d7 100644
--- a/extensions/core/commandlog/persistence-jdo/src/main/java/org/apache/isis/extensions/commandlog/jdo/dom/CommandLogEntry.java
+++ b/extensions/core/commandlog/persistence-jdo/src/main/java/org/apache/isis/extensions/commandlog/jdo/dom/CommandLogEntry.java
@@ -188,10 +188,10 @@ import lombok.Setter;
                                    // which SQL Server doesn't understand.  However, as workaround, SQL Server *does* understand FETCH NEXT 2 ROWS ONLY
 
     @Query(
-            name  = Nq.FIND_NOT_YET_REPLAYED,
+            name  = Nq.FIND_BY_REPLAY_STATE,
             value = "SELECT "
                   + "  FROM " + CommandLogEntry.FQCN + " "
-                  + " WHERE replayState == 'PENDING' "
+                  + " WHERE replayState == :replayState "
                   + " ORDER BY this.timestamp ASC "
                   + " RANGE 0,10"),    // same as batch size
 })
@@ -206,14 +206,6 @@ extends org.apache.isis.extensions.commandlog.applib.dom.CommandLogEntry {
 
     protected final static String FQCN = "org.apache.isis.extensions.commandlog.jdo.entities.CommandJdo";
 
-    /**
-     * Intended for use on primary system.
-     *
-     * @param command - framework's representation of the action invocation or property edit to be performed
-     */
-    public CommandLogEntry(final Command command) {
-        super(command);
-    }
 
     /**
      * Intended for use on secondary (replay) system.
diff --git a/extensions/core/commandlog/persistence-jdo/src/test/java/org/apache/isis/extensions/commandlog/jdo/CommandLog_IntegTest.java b/extensions/core/commandlog/persistence-jdo/src/test/java/org/apache/isis/extensions/commandlog/jdo/CommandLog_IntegTest.java
index b69c6aad4d..6f3c43df5e 100644
--- a/extensions/core/commandlog/persistence-jdo/src/test/java/org/apache/isis/extensions/commandlog/jdo/CommandLog_IntegTest.java
+++ b/extensions/core/commandlog/persistence-jdo/src/test/java/org/apache/isis/extensions/commandlog/jdo/CommandLog_IntegTest.java
@@ -21,6 +21,7 @@ package org.apache.isis.extensions.commandlog.jdo;
 import org.springframework.boot.SpringBootConfiguration;
 import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
 import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.ComponentScan;
 import org.springframework.context.annotation.Import;
 import org.springframework.context.annotation.PropertySource;
 import org.springframework.context.annotation.PropertySources;
@@ -29,6 +30,9 @@ import org.springframework.test.context.ActiveProfiles;
 import org.apache.isis.core.config.presets.IsisPresets;
 import org.apache.isis.core.runtimeservices.IsisModuleCoreRuntimeServices;
 import org.apache.isis.extensions.commandlog.applib.integtest.CommandLogIntegTestAbstract;
+import org.apache.isis.extensions.commandlog.applib.integtest.model.CommandLogTestDomainModel;
+import org.apache.isis.extensions.commandlog.jdo.model.Counter;
+import org.apache.isis.extensions.commandlog.jdo.model.CounterRepository;
 import org.apache.isis.security.bypass.IsisModuleSecurityBypass;
 
 @SpringBootTest(
@@ -46,10 +50,15 @@ public class CommandLog_IntegTest extends CommandLogIntegTestAbstract {
             IsisModuleExtCommandLogPersistenceJdo.class,
     })
     @PropertySources({
-            @PropertySource(IsisPresets.UseLog4j2Test),
+            @PropertySource(IsisPresets.UseLog4j2Test)
     })
+    @ComponentScan(basePackageClasses = {AppManifest.class, CommandLogTestDomainModel.class, CounterRepository.class})
     public static class AppManifest {
     }
 
 
+    protected org.apache.isis.extensions.commandlog.applib.integtest.model.Counter newCounter() {
+        return Counter.builder().name("Fred").build();
+    }
+
 }
diff --git a/extensions/core/commandlog/persistence-jdo/src/test/java/org/apache/isis/extensions/commandlog/jdo/model/Counter.java b/extensions/core/commandlog/persistence-jdo/src/test/java/org/apache/isis/extensions/commandlog/jdo/model/Counter.java
new file mode 100644
index 0000000000..6c503c1344
--- /dev/null
+++ b/extensions/core/commandlog/persistence-jdo/src/test/java/org/apache/isis/extensions/commandlog/jdo/model/Counter.java
@@ -0,0 +1,52 @@
+package org.apache.isis.extensions.commandlog.jdo.model;
+
+import javax.inject.Named;
+import javax.jdo.annotations.Column;
+import javax.jdo.annotations.DatastoreIdentity;
+import javax.jdo.annotations.IdGeneratorStrategy;
+import javax.jdo.annotations.PersistenceCapable;
+import javax.jdo.annotations.PrimaryKey;
+
+import org.apache.isis.applib.annotation.DomainObject;
+import org.apache.isis.applib.annotation.Editing;
+import org.apache.isis.applib.annotation.Nature;
+import org.apache.isis.applib.annotation.Property;
+import org.apache.isis.applib.annotation.Publishing;
+
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@PersistenceCapable(
+        schema = "public",
+        table = "Counter"
+)
+@DatastoreIdentity(strategy = IdGeneratorStrategy.IDENTITY)
+@Named("commandlog.test.Counter")
+@DomainObject(nature = Nature.ENTITY)
+@NoArgsConstructor
+@Builder
+@AllArgsConstructor(access = AccessLevel.PRIVATE)
+public class Counter extends org.apache.isis.extensions.commandlog.applib.integtest.model.Counter {
+
+    @PrimaryKey
+    @Getter @Setter
+    private Long id;
+
+    @Column(allowsNull = "false")
+    @Getter @Setter
+    private String name;
+
+    @Column(allowsNull = "true")
+    @Getter @Setter
+    private Long num;
+
+    @Column(allowsNull = "true")
+    @Getter @Setter
+    private Long num2;
+
+
+}
diff --git a/extensions/core/commandlog/persistence-jdo/src/test/java/org/apache/isis/extensions/commandlog/jdo/model/CounterRepository.java b/extensions/core/commandlog/persistence-jdo/src/test/java/org/apache/isis/extensions/commandlog/jdo/model/CounterRepository.java
new file mode 100644
index 0000000000..5ceb2d7bd4
--- /dev/null
+++ b/extensions/core/commandlog/persistence-jdo/src/test/java/org/apache/isis/extensions/commandlog/jdo/model/CounterRepository.java
@@ -0,0 +1,36 @@
+package org.apache.isis.extensions.commandlog.jdo.model;
+
+import java.util.List;
+
+import javax.inject.Inject;
+
+import org.springframework.stereotype.Repository;
+
+import org.apache.isis.applib.services.repository.RepositoryService;
+
+@Repository
+public class CounterRepository
+        implements org.apache.isis.extensions.commandlog.applib.integtest.model.CounterRepository<Counter> {
+
+    final RepositoryService repositoryService;
+
+    @Inject
+    public CounterRepository( RepositoryService repositoryService) {
+        this.repositoryService = repositoryService;
+    }
+
+    @Override
+    public List<Counter> find() {
+        return repositoryService.allInstances(Counter.class);
+    }
+
+    @Override
+    public Counter persist(Counter counter) {
+        return repositoryService.persist(counter);
+    }
+
+    @Override
+    public void remove(Counter counter) {
+        repositoryService.remove(counter);
+    }
+}
diff --git a/extensions/core/commandlog/persistence-jdo/src/test/resources/application-test.yml b/extensions/core/commandlog/persistence-jdo/src/test/resources/application-test.yml
new file mode 100644
index 0000000000..4edf0c2667
--- /dev/null
+++ b/extensions/core/commandlog/persistence-jdo/src/test/resources/application-test.yml
@@ -0,0 +1,29 @@
+#  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.
+isis:
+  persistence:
+    schema:
+      auto-create-schemas: "ISISEXTCOMMANDLOG"
+
+  extensions:
+    session-log:
+      auto-logout-on-restart: false
+
+spring:
+  jpa:
+    show-sql: true
+
diff --git a/extensions/core/commandlog/persistence-jpa/src/main/java/org/apache/isis/extensions/commandlog/jpa/IsisModuleExtCommandLogPersistenceJpa.java b/extensions/core/commandlog/persistence-jpa/src/main/java/org/apache/isis/extensions/commandlog/jpa/IsisModuleExtCommandLogPersistenceJpa.java
index 9260c5707c..25c8697d0b 100644
--- a/extensions/core/commandlog/persistence-jpa/src/main/java/org/apache/isis/extensions/commandlog/jpa/IsisModuleExtCommandLogPersistenceJpa.java
+++ b/extensions/core/commandlog/persistence-jpa/src/main/java/org/apache/isis/extensions/commandlog/jpa/IsisModuleExtCommandLogPersistenceJpa.java
@@ -24,7 +24,9 @@ import org.springframework.context.annotation.Import;
 
 import org.apache.isis.extensions.commandlog.applib.IsisModuleExtCommandLogApplib;
 import org.apache.isis.extensions.commandlog.jpa.dom.CommandLogEntry;
+import org.apache.isis.extensions.commandlog.jpa.dom.CommandLogEntryPK;
 import org.apache.isis.extensions.commandlog.jpa.dom.CommandLogEntryRepository;
+import org.apache.isis.persistence.jpa.eclipselink.IsisModulePersistenceJpaEclipselink;
 
 /**
  * @since 2.0 {@index}
@@ -32,16 +34,19 @@ import org.apache.isis.extensions.commandlog.jpa.dom.CommandLogEntryRepository;
 @Configuration
 @Import({
         // modules
+        // IsisModuleTestingFixturesApplib.class,
         IsisModuleExtCommandLogApplib.class,
+        IsisModulePersistenceJpaEclipselink.class,
 
         // @Service's
         CommandLogEntryRepository.class,
+        CommandLogEntryPK.Stringifier.class,
 
         // entities
         CommandLogEntry.class
 })
 @EntityScan(basePackageClasses = {
-    CommandLogEntry.class,
+    CommandLogEntry.class
 })
 public class IsisModuleExtCommandLogPersistenceJpa {
 
diff --git a/extensions/core/commandlog/persistence-jpa/src/main/java/org/apache/isis/extensions/commandlog/jpa/dom/CommandLogEntry.java b/extensions/core/commandlog/persistence-jpa/src/main/java/org/apache/isis/extensions/commandlog/jpa/dom/CommandLogEntry.java
index 6e7008224f..309efc5354 100644
--- a/extensions/core/commandlog/persistence-jpa/src/main/java/org/apache/isis/extensions/commandlog/jpa/dom/CommandLogEntry.java
+++ b/extensions/core/commandlog/persistence-jpa/src/main/java/org/apache/isis/extensions/commandlog/jpa/dom/CommandLogEntry.java
@@ -24,19 +24,23 @@ import javax.inject.Named;
 import javax.persistence.Basic;
 import javax.persistence.Column;
 import javax.persistence.Convert;
+import javax.persistence.EmbeddedId;
 import javax.persistence.Entity;
 import javax.persistence.EntityListeners;
 import javax.persistence.EnumType;
 import javax.persistence.Enumerated;
 import javax.persistence.FetchType;
 import javax.persistence.Id;
+import javax.persistence.IdClass;
 import javax.persistence.Index;
 import javax.persistence.JoinColumn;
+import javax.persistence.JoinColumns;
 import javax.persistence.Lob;
 import javax.persistence.ManyToOne;
 import javax.persistence.NamedQueries;
 import javax.persistence.NamedQuery;
 import javax.persistence.Table;
+import javax.persistence.Transient;
 import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
 
 import org.apache.isis.applib.annotation.DomainObject;
@@ -46,6 +50,7 @@ import org.apache.isis.applib.services.bookmark.Bookmark;
 import org.apache.isis.applib.services.command.Command;
 import org.apache.isis.persistence.jpa.applib.integration.IsisEntityListener;
 import org.apache.isis.persistence.jpa.integration.typeconverters.applib.IsisBookmarkConverter;
+import org.apache.isis.persistence.jpa.integration.typeconverters.java.util.JavaUtilUuidConverter;
 import org.apache.isis.persistence.jpa.integration.typeconverters.schema.v2.IsisCommandDtoConverter;
 import org.apache.isis.schema.cmd.v2.CommandDto;
 
@@ -183,10 +188,10 @@ import lombok.Setter;
                   + "   AND cl.completedAt is not null "
                   + " ORDER BY cl.timestamp DESC"), // programmatic LIMIT 1
     @NamedQuery(
-            name  = Nq.FIND_NOT_YET_REPLAYED,
+            name  = Nq.FIND_BY_REPLAY_STATE,
             query = "SELECT cl "
                   + "  FROM CommandLogEntry cl "
-                  + " WHERE cl.replayState = 'PENDING' "
+                  + " WHERE cl.replayState = :replayState "
                   + " ORDER BY cl.timestamp ASC"), // programmatic LIMIT 10
 })
 @Named(CommandLogEntry.LOGICAL_TYPE_NAME)
@@ -198,15 +203,6 @@ import lombok.Setter;
 @NoArgsConstructor
 public class CommandLogEntry extends org.apache.isis.extensions.commandlog.applib.dom.CommandLogEntry {
 
-    /**
-     * Intended for use on primary system.
-     *
-     * @param command - representation of the action invocation or property edit to be performed
-     */
-    public CommandLogEntry(final Command command) {
-        super(command);
-    }
-
     /**
      * Intended for use on secondary (replay) system.
      *
@@ -221,11 +217,18 @@ public class CommandLogEntry extends org.apache.isis.extensions.commandlog.appli
         super(commandDto, replayState, targetIndex);
     }
 
-    @Id
-    @Column(nullable = InteractionId.NULLABLE, length = InteractionId.MAX_LENGTH)
+    @Transient
     @InteractionId
-    @Getter @Setter
-    private UUID interactionId;
+    public UUID getInteractionId() {
+        return interactionId != null ? interactionId.getInteractionId() : null;
+    }
+    @Transient
+    public void setInteractionId(UUID interactionId) {
+        this.interactionId = new CommandLogEntryPK(interactionId);
+    }
+
+    @EmbeddedId
+    private CommandLogEntryPK interactionId;
 
 
     @Column(nullable = Username.NULLABLE, length = Username.MAX_LENGTH)
@@ -248,7 +251,9 @@ public class CommandLogEntry extends org.apache.isis.extensions.commandlog.appli
 
 
     @ManyToOne
-    @JoinColumn(name = Parent.NAME, nullable = Parent.NULLABLE)
+    @JoinColumns({
+        @JoinColumn(name = Parent.NAME, nullable = Parent.NULLABLE, referencedColumnName = InteractionId.NAME)
+    })
     @Parent
     @Getter
     private CommandLogEntry parent;
diff --git a/extensions/core/commandlog/persistence-jpa/src/main/java/org/apache/isis/extensions/commandlog/jpa/dom/CommandLogEntryPK.java b/extensions/core/commandlog/persistence-jpa/src/main/java/org/apache/isis/extensions/commandlog/jpa/dom/CommandLogEntryPK.java
new file mode 100644
index 0000000000..79c81a1bf6
--- /dev/null
+++ b/extensions/core/commandlog/persistence-jpa/src/main/java/org/apache/isis/extensions/commandlog/jpa/dom/CommandLogEntryPK.java
@@ -0,0 +1,58 @@
+package org.apache.isis.extensions.commandlog.jpa.dom;
+
+import java.io.Serializable;
+import java.util.UUID;
+
+import javax.persistence.Column;
+import javax.persistence.Convert;
+import javax.persistence.Embeddable;
+
+import org.springframework.stereotype.Component;
+
+import org.apache.isis.applib.services.bookmark.BookmarkService;
+import org.apache.isis.extensions.commandlog.applib.dom.CommandLogEntry;
+import org.apache.isis.persistence.jpa.integration.typeconverters.java.util.JavaUtilUuidConverter;
+
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@EqualsAndHashCode(of = {"interactionId"})
+@NoArgsConstructor
+@AllArgsConstructor
+@Embeddable
+public class CommandLogEntryPK implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @Convert(converter = JavaUtilUuidConverter.class)
+    @Column(name = CommandLogEntry.InteractionId.NAME, nullable = CommandLogEntry.InteractionId.NULLABLE, length = CommandLogEntry.InteractionId.MAX_LENGTH)
+    @Getter(AccessLevel.PACKAGE)
+    private UUID interactionId;
+
+    @Override
+    public String toString() {
+        return interactionId != null ? interactionId.toString() : null;
+    }
+
+
+    @Component
+    public static class Stringifier implements BookmarkService.Stringifier {
+        @Override
+        public Class<?> handles() {
+            return CommandLogEntryPK.class;
+        }
+
+        @Override
+        public String stringify(Object value) {
+            return ((CommandLogEntryPK)value).getInteractionId().toString();
+        }
+
+        @Override
+        public Object parse(String stringified) {
+            return new CommandLogEntryPK(UUID.fromString(stringified));
+        }
+    }
+}
diff --git a/extensions/core/commandlog/persistence-jpa/src/main/java/org/apache/isis/extensions/commandlog/jpa/dom/CommandLogEntryRepository.java b/extensions/core/commandlog/persistence-jpa/src/main/java/org/apache/isis/extensions/commandlog/jpa/dom/CommandLogEntryRepository.java
index 409b1c50b1..a6c8282794 100644
--- a/extensions/core/commandlog/persistence-jpa/src/main/java/org/apache/isis/extensions/commandlog/jpa/dom/CommandLogEntryRepository.java
+++ b/extensions/core/commandlog/persistence-jpa/src/main/java/org/apache/isis/extensions/commandlog/jpa/dom/CommandLogEntryRepository.java
@@ -18,6 +18,9 @@
  */
 package org.apache.isis.extensions.commandlog.jpa.dom;
 
+import java.util.Optional;
+import java.util.UUID;
+
 import javax.inject.Named;
 
 import org.springframework.beans.factory.annotation.Qualifier;
@@ -25,6 +28,7 @@ import org.springframework.stereotype.Service;
 
 import org.apache.isis.applib.annotation.PriorityPrecedence;
 import org.apache.isis.extensions.commandlog.jpa.IsisModuleExtCommandLogPersistenceJpa;
+import org.apache.isis.persistence.jpa.integration.typeconverters.java.util.JavaUtilUuidConverter;
 
 /**
  * Provides supporting functionality for querying and persisting
@@ -43,5 +47,8 @@ extends org.apache.isis.extensions.commandlog.applib.dom.CommandLogEntryReposito
         super(CommandLogEntry.class);
     }
 
-
+    @Override
+    protected Object convert(UUID interactionId) {
+        return new CommandLogEntryPK(interactionId);
+    }
 }
diff --git a/extensions/core/commandlog/persistence-jpa/src/main/resources/META-INF/orm-commandlog.template b/extensions/core/commandlog/persistence-jpa/src/main/resources/META-INF/orm-commandlog.template
index d2c2c83c31..b02b2c6d8e 100644
--- a/extensions/core/commandlog/persistence-jpa/src/main/resources/META-INF/orm-commandlog.template
+++ b/extensions/core/commandlog/persistence-jpa/src/main/resources/META-INF/orm-commandlog.template
@@ -25,8 +25,8 @@
 
 	<!-- rename file to .xml then customize-->
 
-	<entity class="org.apache.isis.extensions.commandlog.jpa.entities.CommandJpa">
-		<table schema="isisExtensionsCommandLog" name="Command"/>
+	<entity class="org.apache.isis.extensions.commandlog.jpa.dom.CommandLogEntry">
+		<table schema="isisExtCommandLog" name="Command"/>
 	</entity>
 
-</entity-mappings>
\ No newline at end of file
+</entity-mappings>
diff --git a/extensions/core/commandlog/persistence-jpa/src/test/java/org/apache/isis/extensions/commandlog/jpa/CommandLog_IntegTest.java b/extensions/core/commandlog/persistence-jpa/src/test/java/org/apache/isis/extensions/commandlog/jpa/CommandLog_IntegTest.java
index a96e609809..977e14212b 100644
--- a/extensions/core/commandlog/persistence-jpa/src/test/java/org/apache/isis/extensions/commandlog/jpa/CommandLog_IntegTest.java
+++ b/extensions/core/commandlog/persistence-jpa/src/test/java/org/apache/isis/extensions/commandlog/jpa/CommandLog_IntegTest.java
@@ -20,7 +20,9 @@ package org.apache.isis.extensions.commandlog.jpa;
 
 import org.springframework.boot.SpringBootConfiguration;
 import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.domain.EntityScan;
 import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.ComponentScan;
 import org.springframework.context.annotation.Import;
 import org.springframework.context.annotation.PropertySource;
 import org.springframework.context.annotation.PropertySources;
@@ -29,6 +31,8 @@ import org.springframework.test.context.ActiveProfiles;
 import org.apache.isis.core.config.presets.IsisPresets;
 import org.apache.isis.core.runtimeservices.IsisModuleCoreRuntimeServices;
 import org.apache.isis.extensions.commandlog.applib.integtest.CommandLogIntegTestAbstract;
+import org.apache.isis.extensions.commandlog.applib.integtest.model.CommandLogTestDomainModel;
+import org.apache.isis.extensions.commandlog.jpa.model.Counter;
 import org.apache.isis.security.bypass.IsisModuleSecurityBypass;
 
 @SpringBootTest(
@@ -46,10 +50,16 @@ public class CommandLog_IntegTest extends CommandLogIntegTestAbstract {
             IsisModuleExtCommandLogPersistenceJpa.class,
     })
     @PropertySources({
-            @PropertySource(IsisPresets.UseLog4j2Test),
+            @PropertySource(IsisPresets.UseLog4j2Test)
     })
+    @EntityScan(basePackageClasses = {Counter.class})
+    @ComponentScan(basePackageClasses = {AppManifest.class, CommandLogTestDomainModel.class})
     public static class AppManifest {
     }
 
 
+    protected org.apache.isis.extensions.commandlog.applib.integtest.model.Counter newCounter() {
+        return Counter.builder().name("Fred").build();
+    }
+
 }
diff --git a/extensions/core/commandlog/persistence-jpa/src/test/java/org/apache/isis/extensions/commandlog/jpa/model/Counter.java b/extensions/core/commandlog/persistence-jpa/src/test/java/org/apache/isis/extensions/commandlog/jpa/model/Counter.java
new file mode 100644
index 0000000000..6546e7e7b4
--- /dev/null
+++ b/extensions/core/commandlog/persistence-jpa/src/test/java/org/apache/isis/extensions/commandlog/jpa/model/Counter.java
@@ -0,0 +1,55 @@
+package org.apache.isis.extensions.commandlog.jpa.model;
+
+import javax.inject.Named;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.Table;
+
+import org.apache.isis.applib.annotation.DomainObject;
+import org.apache.isis.applib.annotation.Editing;
+import org.apache.isis.applib.annotation.Nature;
+import org.apache.isis.applib.annotation.Property;
+import org.apache.isis.applib.annotation.Publishing;
+
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@Entity
+@Table(
+        schema = "public",
+        name = "Counter"
+)
+@Named("commandlog.test.Counter")
+@DomainObject(nature = Nature.ENTITY)
+@NoArgsConstructor
+@Builder
+@AllArgsConstructor(access = AccessLevel.PRIVATE)
+public class Counter extends org.apache.isis.extensions.commandlog.applib.integtest.model.Counter {
+
+    @Id
+    @GeneratedValue
+    @Getter @Setter
+    private Long id;
+
+    @Column(nullable = false)
+    @Getter @Setter
+    private String name;
+
+    @Column(nullable = true)
+    @Property(editing = Editing.ENABLED, commandPublishing = Publishing.ENABLED)
+    @Getter @Setter
+    private Long num;
+
+    @Column(nullable = true)
+    @Property(editing = Editing.ENABLED, commandPublishing = Publishing.DISABLED)
+    @Getter @Setter
+    private Long num2;
+
+
+}
diff --git a/extensions/core/commandlog/persistence-jpa/src/test/java/org/apache/isis/extensions/commandlog/jpa/model/CounterRepository.java b/extensions/core/commandlog/persistence-jpa/src/test/java/org/apache/isis/extensions/commandlog/jpa/model/CounterRepository.java
new file mode 100644
index 0000000000..d01c157290
--- /dev/null
+++ b/extensions/core/commandlog/persistence-jpa/src/test/java/org/apache/isis/extensions/commandlog/jpa/model/CounterRepository.java
@@ -0,0 +1,27 @@
+package org.apache.isis.extensions.commandlog.jpa.model;
+
+import java.util.List;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface CounterRepository
+        extends JpaRepository<Counter, Integer>,
+        org.apache.isis.extensions.commandlog.applib.integtest.model.CounterRepository<Counter> {
+
+    @Override
+    default List<Counter> find() {
+        return findAll();
+    }
+
+    @Override
+    default Counter persist(Counter counter) {
+        return save(counter);
+    }
+
+    @Override
+    default void remove(Counter counter) {
+        delete(counter);
+    }
+}
diff --git a/extensions/core/commandlog/persistence-jpa/src/test/resources/META-INF/persistence.xml b/extensions/core/commandlog/persistence-jpa/src/test/resources/META-INF/persistence.xml
new file mode 100644
index 0000000000..a7f0675630
--- /dev/null
+++ b/extensions/core/commandlog/persistence-jpa/src/test/resources/META-INF/persistence.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<persistence
+        version="2.1"
+        xmlns="http://xmlns.jcp.org/xml/ns/persistence"
+        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+        xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
+
+    <persistence-unit name="command-log-jpa"/>
+
+</persistence>
diff --git a/extensions/core/commandlog/persistence-jpa/src/test/resources/application-test.yml b/extensions/core/commandlog/persistence-jpa/src/test/resources/application-test.yml
new file mode 100644
index 0000000000..4edf0c2667
--- /dev/null
+++ b/extensions/core/commandlog/persistence-jpa/src/test/resources/application-test.yml
@@ -0,0 +1,29 @@
+#  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.
+isis:
+  persistence:
+    schema:
+      auto-create-schemas: "ISISEXTCOMMANDLOG"
+
+  extensions:
+    session-log:
+      auto-logout-on-restart: false
+
+spring:
+  jpa:
+    show-sql: true
+
diff --git a/extensions/core/commandlog/pom.xml b/extensions/core/commandlog/pom.xml
index aa42900d28..14de923c6c 100644
--- a/extensions/core/commandlog/pom.xml
+++ b/extensions/core/commandlog/pom.xml
@@ -31,25 +31,17 @@
 		<dependencies>
 			<dependency>
 				<groupId>org.apache.isis.extensions</groupId>
-				<artifactId>isis-extensions-commandlog-applib</artifactId>
+				<artifactId>isis-extensions</artifactId>
 				<version>2.0.0-SNAPSHOT</version>
+				<scope>import</scope>
+				<type>pom</type>
 			</dependency>
 			<dependency>
-				<groupId>org.apache.isis.extensions</groupId>
-				<artifactId>isis-extensions-commandlog-applib</artifactId>
-				<version>2.0.0-SNAPSHOT</version>
-				<scope>test</scope>
-				<type>test-jar</type>
-			</dependency>
-			<dependency>
-				<groupId>org.apache.isis.extensions</groupId>
-				<artifactId>isis-extensions-commandlog-persistence-jdo</artifactId>
-				<version>2.0.0-SNAPSHOT</version>
-			</dependency>
-			<dependency>
-				<groupId>org.apache.isis.extensions</groupId>
-				<artifactId>isis-extensions-commandlog-persistence-jpa</artifactId>
+				<groupId>org.apache.isis.testing</groupId>
+				<artifactId>isis-testing</artifactId>
 				<version>2.0.0-SNAPSHOT</version>
+				<scope>import</scope>
+				<type>pom</type>
 			</dependency>
 		</dependencies>
 	</dependencyManagement>
diff --git a/extensions/pom.xml b/extensions/pom.xml
index 613665b665..42ae48780c 100644
--- a/extensions/pom.xml
+++ b/extensions/pom.xml
@@ -76,6 +76,13 @@
 				<artifactId>isis-extensions-commandlog-applib</artifactId>
 				<version>2.0.0-SNAPSHOT</version>
 			</dependency>
+			<dependency>
+				<groupId>org.apache.isis.extensions</groupId>
+				<artifactId>isis-extensions-commandlog-applib</artifactId>
+				<version>2.0.0-SNAPSHOT</version>
+				<scope>test</scope>
+				<type>test-jar</type>
+			</dependency>
 			<dependency>
 				<groupId>org.apache.isis.extensions</groupId>
 				<artifactId>isis-extensions-commandlog-persistence-jdo</artifactId>
@@ -127,6 +134,13 @@
 				<artifactId>isis-extensions-executionlog-applib</artifactId>
 				<version>2.0.0-SNAPSHOT</version>
 			</dependency>
+			<dependency>
+				<groupId>org.apache.isis.extensions</groupId>
+				<artifactId>isis-extensions-executionlog-applib</artifactId>
+				<version>2.0.0-SNAPSHOT</version>
+				<scope>test</scope>
+				<type>test-jar</type>
+			</dependency>
 			<dependency>
 				<groupId>org.apache.isis.extensions</groupId>
 				<artifactId>isis-extensions-executionlog-persistence-jdo</artifactId>
diff --git a/extensions/security/sessionlog/applib/src/test/java/org/apache/isis/sessionlog/applib/SessionLogIntegTestAbstract.java b/extensions/security/sessionlog/applib/src/test/java/org/apache/isis/sessionlog/applib/SessionLogIntegTestAbstract.java
index ad8ae82a30..b282f82efb 100644
--- a/extensions/security/sessionlog/applib/src/test/java/org/apache/isis/sessionlog/applib/SessionLogIntegTestAbstract.java
+++ b/extensions/security/sessionlog/applib/src/test/java/org/apache/isis/sessionlog/applib/SessionLogIntegTestAbstract.java
@@ -66,7 +66,7 @@ public abstract class SessionLogIntegTestAbstract extends IsisIntegrationTestAbs
 
         List<? extends SessionLogEntry> sessions;
         SessionLogEntry session;
-        Optional<SessionLogEntry> sessionIfAny;
+        Optional<? extends SessionLogEntry> sessionIfAny;
 
         // given
         sessions = sessionLogEntryRepository.findActiveSessions();
@@ -138,6 +138,6 @@ public abstract class SessionLogIntegTestAbstract extends IsisIntegrationTestAbs
     }
 
     @Inject @Qualifier("default") SessionLogService sessionLogService;
-    @Inject SessionLogEntryRepository sessionLogEntryRepository;
+    @Inject SessionLogEntryRepository<? extends SessionLogEntry> sessionLogEntryRepository;
 
 }
diff --git a/incubator/viewers/graphql/viewer/src/test/java/org/apache/isis/viewer/graphql/viewer/source/gqltestdomain/E1.java b/incubator/viewers/graphql/viewer/src/test/java/org/apache/isis/viewer/graphql/viewer/source/gqltestdomain/E1.java
index e532b2c30a..8f1ba43c33 100644
--- a/incubator/viewers/graphql/viewer/src/test/java/org/apache/isis/viewer/graphql/viewer/source/gqltestdomain/E1.java
+++ b/incubator/viewers/graphql/viewer/src/test/java/org/apache/isis/viewer/graphql/viewer/source/gqltestdomain/E1.java
@@ -33,6 +33,8 @@ import org.apache.isis.applib.annotation.Property;
 import lombok.Getter;
 import lombok.Setter;
 
+import graphql.com.google.common.collect.ComparisonChain;
+
 //@Profile("demo-jpa")
 @Entity
 @Table(
diff --git a/persistence/jdo/integration/src/main/java/org/apache/isis/persistence/jdo/integration/changetracking/EntityChangeTrackerJdo.java b/persistence/jdo/integration/src/main/java/org/apache/isis/persistence/jdo/integration/changetracking/EntityChangeTrackerJdo.java
index 101b26ddde..db9a940171 100644
--- a/persistence/jdo/integration/src/main/java/org/apache/isis/persistence/jdo/integration/changetracking/EntityChangeTrackerJdo.java
+++ b/persistence/jdo/integration/src/main/java/org/apache/isis/persistence/jdo/integration/changetracking/EntityChangeTrackerJdo.java
@@ -217,7 +217,7 @@ implements
     }
 
     private void enableCommandPublishing() {
-        val alreadySet = persitentChangesEncountered.getAndSet(true);
+        val alreadySet = persistentChangesEncountered.getAndSet(true);
         if(!alreadySet) {
             val command = currentInteraction().getCommand();
             command.updater().setSystemStateChanged(true);
@@ -410,6 +410,6 @@ implements
 
     private final LongAdder numberEntitiesLoaded = new LongAdder();
     private final LongAdder entityChangeEventCount = new LongAdder();
-    private final AtomicBoolean persitentChangesEncountered = new AtomicBoolean();
+    private final AtomicBoolean persistentChangesEncountered = new AtomicBoolean();
 
 }
diff --git a/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/IsisModulePersistenceJpaIntegration.java b/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/IsisModulePersistenceJpaIntegration.java
index 2aa325d788..bf5e38c657 100644
--- a/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/IsisModulePersistenceJpaIntegration.java
+++ b/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/IsisModulePersistenceJpaIntegration.java
@@ -26,7 +26,18 @@ import org.apache.isis.core.runtime.IsisModuleCoreRuntime;
 import org.apache.isis.persistence.jpa.integration.changetracking.PersistenceMetricsServiceJpa;
 import org.apache.isis.persistence.jpa.integration.entity.JpaEntityIntegration;
 import org.apache.isis.persistence.jpa.integration.services.JpaSupportServiceUsingSpring;
-import org.apache.isis.persistence.jpa.integration.typeconverters.image.JavaAwtBufferedImageByteArrayConverter;
+import org.apache.isis.persistence.jpa.integration.typeconverters.applib.IsisBookmarkConverter;
+import org.apache.isis.persistence.jpa.integration.typeconverters.applib.IsisLocalResourcePathConverter;
+import org.apache.isis.persistence.jpa.integration.typeconverters.applib.IsisMarkupConverter;
+import org.apache.isis.persistence.jpa.integration.typeconverters.applib.IsisPasswordConverter;
+import org.apache.isis.persistence.jpa.integration.typeconverters.java.awt.JavaAwtBufferedImageByteArrayConverter;
+import org.apache.isis.persistence.jpa.integration.typeconverters.java.util.JavaUtilUuidConverter;
+import org.apache.isis.persistence.jpa.integration.typeconverters.schema.v2.IsisChangesDtoConverter;
+import org.apache.isis.persistence.jpa.integration.typeconverters.schema.v2.IsisCommandDtoConverter;
+import org.apache.isis.persistence.jpa.integration.typeconverters.schema.v2.IsisInteractionDtoConverter;
+import org.apache.isis.persistence.jpa.integration.typeconverters.schema.v2.IsisOidDtoConverter;
+import org.apache.isis.persistence.jpa.integration.typeconverters.java.time.JavaTimeIsoOffsetTimeConverter;
+import org.apache.isis.persistence.jpa.integration.typeconverters.java.time.JavaTimeIsoZonedDateTimeConverter;
 import org.apache.isis.persistence.jpa.metamodel.IsisModulePersistenceJpaMetamodel;
 
 @Configuration
@@ -46,7 +57,19 @@ import org.apache.isis.persistence.jpa.metamodel.IsisModulePersistenceJpaMetamod
 @EntityScan(basePackageClasses = {
 
         // @Converter's
-        JavaAwtBufferedImageByteArrayConverter.class
+        IsisBookmarkConverter.class,
+        IsisLocalResourcePathConverter.class,
+        IsisMarkupConverter.class,
+        IsisPasswordConverter.class,
+        IsisChangesDtoConverter.class,
+        IsisCommandDtoConverter.class,
+        IsisInteractionDtoConverter.class,
+        IsisOidDtoConverter.class,
+        JavaAwtBufferedImageByteArrayConverter.class,
+        JavaUtilUuidConverter.class,
+        JavaTimeIsoOffsetTimeConverter.class,
+        JavaTimeIsoZonedDateTimeConverter.class
+
 })
 public class IsisModulePersistenceJpaIntegration {
 
diff --git a/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/entity/JpaEntityFacetFactory.java b/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/entity/JpaEntityFacetFactory.java
index 35b5dace81..137c3105a6 100644
--- a/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/entity/JpaEntityFacetFactory.java
+++ b/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/entity/JpaEntityFacetFactory.java
@@ -24,9 +24,11 @@ import java.util.Optional;
 import javax.inject.Inject;
 import javax.persistence.Entity;
 import javax.persistence.EntityManager;
+import javax.persistence.PersistenceException;
 import javax.persistence.PersistenceUnitUtil;
 import javax.persistence.metamodel.EntityType;
 
+import org.eclipse.persistence.exceptions.DescriptorException;
 import org.springframework.data.jpa.repository.JpaContext;
 
 import org.apache.isis.applib.exceptions.unrecoverable.ObjectNotFoundException;
@@ -34,6 +36,7 @@ import org.apache.isis.applib.query.AllInstancesQuery;
 import org.apache.isis.applib.query.NamedQuery;
 import org.apache.isis.applib.query.Query;
 import org.apache.isis.applib.services.bookmark.Bookmark;
+import org.apache.isis.applib.services.bookmark.BookmarkService;
 import org.apache.isis.applib.services.registry.ServiceRegistry;
 import org.apache.isis.applib.services.repository.EntityState;
 import org.apache.isis.applib.services.urlencoding.UrlEncodingService;
@@ -299,9 +302,22 @@ extends FacetFactoryAbstract {
                 return EntityState.PERSISTABLE_ATTACHED;
             }
 
-            val primaryKey = getPersistenceUnitUtil(entityManager).getIdentifier(pojo);
-            if(primaryKey == null) {
-                return EntityState.PERSISTABLE_DETACHED; // an optimization, not strictly required
+            try {
+                val primaryKey = getPersistenceUnitUtil(entityManager).getIdentifier(pojo);
+                if(primaryKey == null) {
+                    return EntityState.PERSISTABLE_DETACHED; // an optimization, not strictly required
+                }
+            } catch(PersistenceException ex) {
+                // horrible hack, but encountered NPEs if using a composite key (eg CommandLogEntry)
+                Throwable cause = ex.getCause();
+                if (cause instanceof DescriptorException) {
+                    DescriptorException descriptorException = (DescriptorException) cause;
+                    Throwable internalException = descriptorException.getInternalException();
+                    if(internalException instanceof NullPointerException) {
+                        return EntityState.PERSISTABLE_DETACHED;
+                    }
+                }
+                throw ex;
             }
 
             //XXX whether DETACHED or REMOVED is currently undecidable (JPA)
@@ -393,6 +409,12 @@ extends FacetFactoryAbstract {
                 return new ByteIdSerializer();
             }
         }
+        Can<BookmarkService.Stringifier> select = serviceRegistry.select(BookmarkService.Stringifier.class);
+        for (BookmarkService.Stringifier stringifier : select.toList()) {
+            if(stringifier.handles() == primaryKeyType) {
+                return new DelegatingSerializer(stringifier);
+            }
+        }
 
         val codec = serviceRegistry.lookupServiceElseFail(UrlEncodingService.class);
         val serializer = serviceRegistry.lookupServiceElseFail(SerializingAdapter.class);
@@ -428,6 +450,15 @@ extends FacetFactoryAbstract {
         @Override String stringify(final Byte id) { return id.toString(); }
         @Override Byte parse(final String stringifiedPrimaryKey) { return Byte.parseByte(stringifiedPrimaryKey); }
     }
+    private static class DelegatingSerializer extends JpaObjectIdSerializer<Object> {
+        private final BookmarkService.Stringifier stringifier;
+        public DelegatingSerializer(BookmarkService.Stringifier stringifier) {
+            super(stringifier.handles());
+            this.stringifier = stringifier;
+        }
+        @Override String stringify(final Object value) { return stringifier.stringify(value); }
+        @Override Object parse(final String stringified) { return  stringifier.parse(stringified); }
+    }
 
     private static class JpaObjectIdSerializerUsingMementos<T> extends JpaObjectIdSerializer<T> {
         private final UrlEncodingService codec;
diff --git a/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/typeconverters/image/JavaAwtBufferedImageByteArrayConverter.java b/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/typeconverters/java/awt/JavaAwtBufferedImageByteArrayConverter.java
similarity index 99%
rename from persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/typeconverters/image/JavaAwtBufferedImageByteArrayConverter.java
rename to persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/typeconverters/java/awt/JavaAwtBufferedImageByteArrayConverter.java
index 5a466c9f77..a12899a7eb 100644
--- a/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/typeconverters/image/JavaAwtBufferedImageByteArrayConverter.java
+++ b/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/typeconverters/java/awt/JavaAwtBufferedImageByteArrayConverter.java
@@ -16,7 +16,7 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.isis.persistence.jpa.integration.typeconverters.image;
+package org.apache.isis.persistence.jpa.integration.typeconverters.java.awt;
 
 import java.awt.image.BufferedImage;
 
diff --git a/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/typeconverters/time/IsoOffsetTimeConverter.java b/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/typeconverters/java/time/JavaTimeIsoOffsetTimeConverter.java
similarity index 90%
copy from persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/typeconverters/time/IsoOffsetTimeConverter.java
copy to persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/typeconverters/java/time/JavaTimeIsoOffsetTimeConverter.java
index 437e24695e..29322cdf6c 100644
--- a/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/typeconverters/time/IsoOffsetTimeConverter.java
+++ b/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/typeconverters/java/time/JavaTimeIsoOffsetTimeConverter.java
@@ -16,17 +16,19 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.isis.persistence.jpa.integration.typeconverters.time;
+package org.apache.isis.persistence.jpa.integration.typeconverters.java.time;
 
 import java.time.OffsetTime;
 import java.time.format.DateTimeFormatter;
 
 import javax.persistence.AttributeConverter;
+import javax.persistence.Converter;
 
 /**
  * @since 2.0 {@index}
  */
-public class IsoOffsetTimeConverter implements AttributeConverter<OffsetTime, String>{
+@Converter(autoApply = true)
+public class JavaTimeIsoOffsetTimeConverter implements AttributeConverter<OffsetTime, String>{
 
     private static final long serialVersionUID = 1L;
 
diff --git a/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/typeconverters/time/IsoZonedDateTimeConverter.java b/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/typeconverters/java/time/JavaTimeIsoZonedDateTimeConverter.java
similarity index 89%
rename from persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/typeconverters/time/IsoZonedDateTimeConverter.java
rename to persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/typeconverters/java/time/JavaTimeIsoZonedDateTimeConverter.java
index a1ba29edcf..3bf5c6e634 100644
--- a/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/typeconverters/time/IsoZonedDateTimeConverter.java
+++ b/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/typeconverters/java/time/JavaTimeIsoZonedDateTimeConverter.java
@@ -16,18 +16,19 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.isis.persistence.jpa.integration.typeconverters.time;
+package org.apache.isis.persistence.jpa.integration.typeconverters.java.time;
 
-import java.time.OffsetTime;
 import java.time.ZonedDateTime;
 import java.time.format.DateTimeFormatter;
 
 import javax.persistence.AttributeConverter;
+import javax.persistence.Converter;
 
 /**
  * @since 2.0 {@index}
  */
-public class IsoZonedDateTimeConverter implements AttributeConverter<ZonedDateTime, String> {
+@Converter(autoApply = true)
+public class JavaTimeIsoZonedDateTimeConverter implements AttributeConverter<ZonedDateTime, String> {
 
     private static final long serialVersionUID = 1L;
 
diff --git a/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/typeconverters/time/IsoOffsetTimeConverter.java b/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/typeconverters/java/util/JavaUtilUuidConverter.java
similarity index 69%
rename from persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/typeconverters/time/IsoOffsetTimeConverter.java
rename to persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/typeconverters/java/util/JavaUtilUuidConverter.java
index 437e24695e..5c5e8016de 100644
--- a/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/typeconverters/time/IsoOffsetTimeConverter.java
+++ b/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/typeconverters/java/util/JavaUtilUuidConverter.java
@@ -16,31 +16,32 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.isis.persistence.jpa.integration.typeconverters.time;
+package org.apache.isis.persistence.jpa.integration.typeconverters.java.util;
 
-import java.time.OffsetTime;
-import java.time.format.DateTimeFormatter;
+import java.util.UUID;
 
 import javax.persistence.AttributeConverter;
+import javax.persistence.Converter;
 
 /**
  * @since 2.0 {@index}
  */
-public class IsoOffsetTimeConverter implements AttributeConverter<OffsetTime, String>{
+@Converter(autoApply = true)
+public class JavaUtilUuidConverter implements AttributeConverter<UUID, String> {
 
     private static final long serialVersionUID = 1L;
 
     @Override
-    public String convertToDatabaseColumn(final OffsetTime offsetTime) {
-        return offsetTime != null
-                ? offsetTime.format(DateTimeFormatter.ISO_OFFSET_TIME)
+    public String convertToDatabaseColumn(final UUID uuid) {
+        return uuid != null
+                ? uuid.toString()
                 : null;
     }
 
     @Override
-    public OffsetTime convertToEntityAttribute(final String datastoreValue) {
+    public UUID convertToEntityAttribute(final String datastoreValue) {
         return datastoreValue != null
-                ? OffsetTime.parse(datastoreValue, DateTimeFormatter.ISO_OFFSET_TIME)
+                ? UUID.fromString(datastoreValue)
                 : null;
     }
 
diff --git a/pom.xml b/pom.xml
index 1aa3bdf604..63f178a0e1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -32,7 +32,7 @@
         <maven.deploy.skip>true</maven.deploy.skip> <!-- don't deploy the aggregator -->
 
 		<maven-timeline.version>1.6</maven-timeline.version>
-    </properties>
+	</properties>
 
 	<scm>
 		<connection>scm:git:https://github.com/apache/isis.git</connection>
@@ -348,6 +348,295 @@
 			</modules>
 		</profile>
 
+		<profile>
+			<id>isis-app-starter-surefire</id>
+			<activation>
+				<property>
+					<name>!skip.isis-app-starter-surefire</name>
+				</property>
+			</activation>
+
+			<properties>
+				<!-- uses maven-surefire-plugin.version, which is declared by spring-boot-starter-parent -->
+				<skipTests>false</skipTests>
+				<skipUTs>${skipTests}</skipUTs>
+				<skipITs>${skipTests}</skipITs>
+				<skipBDDs>${skipTests}</skipBDDs>
+				<surefire-plugin.argLine></surefire-plugin.argLine>
+			</properties>
+
+			<build>
+				<pluginManagement>
+					<plugins>
+						<plugin>
+							<groupId>org.apache.maven.plugins</groupId>
+							<artifactId>maven-surefire-plugin</artifactId>
+							<version>${maven-surefire-plugin.version}</version>
+							<executions>
+								<execution>
+									<id>default-test</id>
+									<phase>test</phase>
+									<goals>
+										<goal>test</goal>
+									</goals>
+									<configuration>
+										<skipTests>${skipUTs}</skipTests>
+										<includes>
+											<include>**/*Test*.java</include>
+										</includes>
+										<excludes>
+											<exclude>**/*Testing.java</exclude>
+											<exclude>**/*IntegTest*.java</exclude>
+											<exclude>**/*Abstract*.java</exclude>
+										</excludes>
+										<useFile>true</useFile>
+										<printSummary>true</printSummary>
+										<reportsDirectory>${project.build.directory}/surefire-unittest-reports</reportsDirectory>
+										<forkCount>1</forkCount>
+										<reuseForks>true</reuseForks>
+										<argLine>${surefire-plugin.argLine}</argLine>
+									</configuration>
+								</execution>
+								<execution>
+									<id>integ-test</id>
+									<phase>integration-test</phase>
+									<goals>
+										<goal>test</goal>
+									</goals>
+									<configuration>
+										<skipTests>${skipITs}</skipTests>
+										<includes>
+											<include>**/*IntegTest*.java</include>
+										</includes>
+										<excludes>
+											<exclude>**/*Abstract*.java</exclude>
+										</excludes>
+										<useFile>true</useFile>
+										<printSummary>true</printSummary>
+										<reportsDirectory>${project.build.directory}/surefire-integtest-reports</reportsDirectory>
+										<forkCount>1</forkCount>
+										<reuseForks>true</reuseForks>
+										<argLine>${surefire-plugin.argLine}</argLine>
+									</configuration>
+								</execution>
+								<execution>
+									<id>bdd-specs</id>
+									<phase>integration-test</phase>
+									<goals>
+										<goal>test</goal>
+									</goals>
+									<configuration>
+										<skipTests>${skipBDDs}</skipTests>
+										<includes>
+											<include>**/*Spec*.java</include>
+										</includes>
+										<excludes>
+											<exclude>**/*Test.java</exclude>
+											<exclude>**/*Testing.java</exclude>
+											<exclude>**/*IntegTest*.java</exclude>
+											<exclude>**/*Abstract*.java</exclude>
+										</excludes>
+										<useFile>true</useFile>
+										<printSummary>true</printSummary>
+										<reportsDirectory>${project.build.directory}/surefire-bddspecs-reports</reportsDirectory>
+										<forkCount>1</forkCount>
+										<reuseForks>true</reuseForks>
+										<argLine>${surefire-plugin.argLine}</argLine>
+									</configuration>
+								</execution>
+							</executions>
+						</plugin>
+						<plugin>
+							<groupId>org.apache.maven.plugins</groupId>
+							<artifactId>maven-surefire-report-plugin</artifactId>
+							<version>${maven-surefire-plugin.version}</version>
+							<configuration>
+								<showSuccess>false</showSuccess>
+							</configuration>
+							<executions>
+								<execution>
+									<id>test</id>
+									<phase>test</phase>
+								</execution>
+								<execution>
+									<id>integration-test</id>
+									<phase>integration-test</phase>
+								</execution>
+								<execution>
+									<id>bdd-specs</id>
+									<phase>integration-test</phase>
+								</execution>
+							</executions>
+						</plugin>
+					</plugins>
+				</pluginManagement>
+			</build>
+		</profile>
+
+		<profile>
+			<id>isis-app-starter-docker</id>
+			<activation>
+				<property>
+					<name>!skip.isis-app-starter-docker</name>
+				</property>
+			</activation>
+			<properties>
+				<jib-maven-plugin.version>3.2.1</jib-maven-plugin.version>
+			</properties>
+			<build>
+				<pluginManagement>
+					<plugins>
+						<plugin>
+							<groupId>com.google.cloud.tools</groupId>
+							<artifactId>jib-maven-plugin</artifactId>
+							<version>${jib-maven-plugin.version}</version>
+						</plugin>
+					</plugins>
+				</pluginManagement>
+			</build>
+		</profile>
+
+		<!-- running: mvn spring-boot:run -->
+		<profile>
+			<id>isis-app-starter-boot</id>
+			<activation>
+				<property>
+					<name>!skip.isis-app-starter-boot</name>
+				</property>
+			</activation>
+			<properties>
+			</properties>
+			<build>
+				<pluginManagement>
+					<plugins>
+						<plugin>
+							<groupId>org.springframework.boot</groupId>
+							<artifactId>spring-boot-maven-plugin</artifactId>
+							<version>${spring-boot.version}</version>
+							<executions>
+								<execution>
+									<goals>
+										<goal>repackage</goal>
+									</goals>
+								</execution>
+							</executions>
+						</plugin>
+					</plugins>
+				</pluginManagement>
+			</build>
+		</profile>
+
+		<profile>
+			<id>apache-release</id>
+			<activation>
+				<property>
+					<name>apache-release</name>
+				</property>
+			</activation>
+			<properties>
+				<skipTests>true</skipTests>
+				<altDeploymentRepository>apache.releases.https::default::https://repository.apache.org/service/local/staging/deploy/maven2</altDeploymentRepository>
+			</properties>
+			<build>
+				<plugins>
+					<!-- We want to sign the artifact, the POM, and all attached artifacts -->
+					<plugin>
+						<groupId>org.apache.maven.plugins</groupId>
+						<artifactId>maven-gpg-plugin</artifactId>
+						<version>3.0.1</version>
+						<executions>
+							<execution>
+								<id>sign-release-artifacts</id>
+								<goals>
+									<goal>sign</goal>
+								</goals>
+							</execution>
+						</executions>
+						<configuration>
+							<gpgArguments>
+								<arg>--digest-algo=SHA512</arg>
+							</gpgArguments>
+						</configuration>
+					</plugin>
+					<plugin>
+						<groupId>net.nicoulaj.maven.plugins</groupId>
+						<artifactId>checksum-maven-plugin</artifactId>
+						<version>1.11</version>
+						<executions>
+							<execution>
+								<id>source-release-checksum</id>
+								<goals>
+									<goal>files</goal>
+								</goals>
+							</execution>
+						</executions>
+						<configuration>
+							<algorithms>
+								<algorithm>SHA-512</algorithm>
+							</algorithms>
+							<csvSummary>false</csvSummary>
+							<fileSets>
+								<fileSet>
+									<directory>${project.build.directory}</directory>
+									<includes>
+										<include>${project.artifactId}-${project.version}-source-release.zip</include>
+										<include>${project.artifactId}-${project.version}-source-release.tar*</include>
+									</includes>
+								</fileSet>
+							</fileSets>
+							<failIfNoFiles>false</failIfNoFiles><!-- usually, no file to do checksum:
+								don't consider error -->
+						</configuration>
+					</plugin>
+				</plugins>
+			</build>
+		</profile>
+
+		<profile>
+			<id>github</id>
+			<activation>
+				<property>
+					<name>github</name>
+				</property>
+			</activation>
+			<distributionManagement>
+				<repository>
+					<id>github</id>
+					<name>Github Releases</name>
+					<url>https://maven.pkg.github.com/apache/isis</url>
+				</repository>
+			</distributionManagement>
+		</profile>
+
+		<profile>
+			<id>nightly-localfs-repo</id>
+			<activation>
+				<property>
+					<name>nightly-localfs-repo</name>
+				</property>
+			</activation>
+			<distributionManagement>
+				<repository>
+					<id>nightly-localfs-repo</id>
+					<name>Temporary Local Filesystem Staging Repository</name>
+					<url>file://${MVN_SNAPSHOTS_PATH}</url>
+				</repository>
+			</distributionManagement>
+			<build>
+				<plugins>
+					<plugin>
+						<artifactId>maven-deploy-plugin</artifactId>
+						<version>2.8.2</version>
+						<configuration>
+							<altDeploymentRepository>
+								nightly-localfs-repo::default::file://${MVN_SNAPSHOTS_PATH}
+							</altDeploymentRepository>
+						</configuration>
+					</plugin>
+				</plugins>
+			</build>
+		</profile>
+
 	</profiles>
 
 </project>
diff --git a/regressiontests/stable-interact/src/test/java/org/apache/isis/testdomain/interact/ActionInteractionTest.java b/regressiontests/stable-interact/src/test/java/org/apache/isis/testdomain/interact/ActionInteractionTest.java
index 9b504e2b20..c82590dcaa 100644
--- a/regressiontests/stable-interact/src/test/java/org/apache/isis/testdomain/interact/ActionInteractionTest.java
+++ b/regressiontests/stable-interact/src/test/java/org/apache/isis/testdomain/interact/ActionInteractionTest.java
@@ -35,6 +35,8 @@ import org.apache.isis.commons.collections.Can;
 import org.apache.isis.commons.internal.collections._Lists;
 import org.apache.isis.core.config.presets.IsisPresets;
 import org.apache.isis.core.metamodel.facets.actions.action.invocation.IdentifierUtil;
+import org.apache.isis.core.metamodel.interactions.InteractionHead;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
 import org.apache.isis.testdomain.conf.Configuration_headless;
 import org.apache.isis.testdomain.model.interaction.Configuration_usingInteractionDomain;
 import org.apache.isis.testdomain.model.interaction.DemoEnum;
@@ -45,6 +47,7 @@ import org.apache.isis.testdomain.model.interaction.InteractionDemo_multiEnum;
 import org.apache.isis.testdomain.model.interaction.InteractionDemo_multiInt;
 import org.apache.isis.testdomain.util.interaction.InteractionTestAbstract;
 
+import lombok.NonNull;
 import lombok.val;
 
 @SpringBootTest(
@@ -134,7 +137,8 @@ class ActionInteractionTest extends InteractionTestAbstract {
 
         // test feature-identifier to command matching ...
         val act = tester.getActionMetaModelElseFail();
-        assertTrue(IdentifierUtil.isCommandForMember(command, act));
+        InteractionHead head = tester.getActionMetaModelElseFail().interactionHead(tester.getActionOwnerElseFail());
+        assertTrue(IdentifierUtil.isCommandForMember(command, head, act));
     }
 
     @Test
@@ -180,7 +184,8 @@ class ActionInteractionTest extends InteractionTestAbstract {
 
         // test feature-identifier to command matching ...
         val act = tester.getActionMetaModelElseFail();
-        assertTrue(IdentifierUtil.isCommandForMember(command, act));
+        InteractionHead head = tester.getActionMetaModelElseFail().interactionHead(tester.getActionOwnerElseFail());
+        assertTrue(IdentifierUtil.isCommandForMember(command, head, act));
     }
 
     @Test