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

ahuber pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/causeway.git


The following commit(s) were added to refs/heads/main by this push:
     new 402b1a92a89 CAUSEWAY-3883: let ActionInteractionHead not implement 
InteractionHead (refactor)
402b1a92a89 is described below

commit 402b1a92a894259e3fc6c6820f564cb6e8c99963
Author: Andi Huber <[email protected]>
AuthorDate: Wed Jun 25 10:09:42 2025 +0200

    CAUSEWAY-3883: let ActionInteractionHead not implement InteractionHead
    (refactor)
    
    - refactor InteractionHead from an interface to a record
    - separation of concerns, such that InteractionHead can now used for the
    wrapper logic as well
    - MixinNamingStrategy is now part of the ProgrammingModel
---
 .../metamodel/context/HasMetaModelContext.java     |   5 +
 .../context/MetaModelContext_usingSpring.java      |   5 +
 .../core/metamodel/facets/DomainEventHelper.java   |  30 ++--
 .../actions/action/invocation/IdentifierUtil.java  | 158 ---------------------
 .../metamodel/interactions/InteractionHead.java    | 125 +++++++++-------
 .../managed/ActionInteractionHead.java             |  26 +---
 .../interactions/managed/ManagedAction.java        |   9 +-
 .../managed/ParameterNegotiationModel.java         |  76 +++++-----
 .../core/metamodel/object/MmDebugUtils.java        |   2 +-
 .../core/metamodel/progmodel/ProgrammingModel.java |  10 ++
 .../schema/SchemaValueMarshallerAbstract.java      |  48 ++++++-
 .../metamodel/spec/feature/HasObjectAction.java    |   3 +-
 .../core/metamodel/spec/feature/MixedInAction.java |   1 +
 .../core/metamodel/spec/feature/MixedInMember.java |   2 +-
 .../core/metamodel/spec/feature/ObjectAction.java  |  15 +-
 .../metamodel/spec/impl/ObjectActionDefault.java   |   8 +-
 .../metamodel/spec/impl/ObjectActionMixedIn.java   |   8 +-
 .../spec/impl/ObjectActionParameterAbstract.java   |   2 +-
 .../spec/impl/ProgrammingModelDefault.java         |  10 +-
 .../spec/impl/_MixedInMemberNamingStrategy.java    |  43 +++---
 .../facets/AbstractTestWithMetaModelContext.java   |   4 +
 .../ViewModelSemanticCheckingFacetFactoryTest.java |   7 +-
 .../spec/impl/MixedInMemberNamingStrategyTest.java |  30 ++--
 .../command/CommandDtoFactoryDefault.java          |   7 +-
 .../executor/MemberExecutorServiceDefault.java     |   7 +-
 .../handlers/DomainObjectInvocationHandler.java    |   4 +-
 ...VersionAnnotationFacetFactoryTest_validate.java |   7 +-
 .../testdomain/interact/ActionInteractionTest.java |   5 +-
 .../commons/model/attrib/HasUiParameter.java       |   2 +-
 .../viewer/commons/model/attrib/UiParameter.java   |   2 +-
 .../viewer/resources/_DomainResourceHelper.java    |   3 +-
 31 files changed, 319 insertions(+), 345 deletions(-)

diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/context/HasMetaModelContext.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/context/HasMetaModelContext.java
index fd2881f5fa6..799a72ff0f8 100644
--- 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/context/HasMetaModelContext.java
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/context/HasMetaModelContext.java
@@ -50,6 +50,7 @@
 import org.apache.causeway.core.metamodel.facets.object.icon.ObjectIconService;
 import org.apache.causeway.core.metamodel.object.ManagedObject;
 import org.apache.causeway.core.metamodel.objectmanager.ObjectManager;
+import org.apache.causeway.core.metamodel.progmodel.ProgrammingModel;
 import org.apache.causeway.core.metamodel.services.command.CommandDtoFactory;
 import org.apache.causeway.core.metamodel.services.message.MessageBroker;
 import org.apache.causeway.core.metamodel.spec.ObjectSpecification;
@@ -74,6 +75,10 @@ default CausewaySystemEnvironment getSystemEnvironment() {
     default CausewayConfiguration getConfiguration() {
         return getMetaModelContext().getConfiguration();
     }
+    
+    default ProgrammingModel getProgrammingModel() {
+        return getMetaModelContext().getProgrammingModel();
+    }
 
     default ServiceInjector getServiceInjector() {
         return getMetaModelContext().getServiceInjector();
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/context/MetaModelContext_usingSpring.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/context/MetaModelContext_usingSpring.java
index dc8c32a46ae..55fd93555c0 100644
--- 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/context/MetaModelContext_usingSpring.java
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/context/MetaModelContext_usingSpring.java
@@ -48,6 +48,7 @@
 import org.apache.causeway.core.metamodel.facets.object.icon.ObjectIconService;
 import org.apache.causeway.core.metamodel.object.ManagedObject;
 import org.apache.causeway.core.metamodel.objectmanager.ObjectManager;
+import org.apache.causeway.core.metamodel.progmodel.ProgrammingModel;
 import org.apache.causeway.core.metamodel.services.command.CommandDtoFactory;
 import org.apache.causeway.core.metamodel.specloader.SpecificationLoader;
 import 
org.apache.causeway.core.security.authentication.manager.AuthenticationManager;
@@ -78,6 +79,10 @@ void onDestroy() {
     private final CausewayConfiguration configuration =
     getSingletonElseFail(CausewayConfiguration.class);
 
+    @Getter(lazy=true)
+    private final ProgrammingModel programmingModel =
+    getSingletonElseFail(ProgrammingModel.class);
+    
     @Getter(lazy=true)
     private final ServiceInjector serviceInjector =
     getSingletonElseFail(ServiceInjector.class);
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/DomainEventHelper.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/DomainEventHelper.java
index 95b8991c7f3..2962325caf6 100644
--- 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/DomainEventHelper.java
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/DomainEventHelper.java
@@ -23,6 +23,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Optional;
 
 import org.jspecify.annotations.NonNull;
 import org.jspecify.annotations.Nullable;
@@ -95,7 +96,7 @@ public void postEventForAction(
                 uncheckedCast(existingEvent.getClass()), existingEvent, 
objectAction, facetHolder,
                 head, argumentAdapters, resultPojo);
     }
-
+    
     private @Nullable <S> ActionDomainEvent<S> postEventForAction(
             final AbstractDomainEvent.Phase phase,
             final Class<? extends ActionDomainEvent<S>> eventType,
@@ -123,9 +124,9 @@ public void postEventForAction(
 
                 // copy over if mixee is present
                 if (event != null) {
-                    head.getMixee()
-                    .ifPresent(mixedInAdapter->
-                        event.setMixee(mixedInAdapter.getPojo()));
+                    mixee(head)
+                        .ifPresent(mixedInAdapter->
+                            event.setMixee(mixedInAdapter.getPojo()));
                 }
 
                 if(objectAction != null) {  // should always be the case...
@@ -255,9 +256,9 @@ public <S, T> PropertyDomainEvent<S, T> 
postEventForProperty(
                 event = newPropertyDomainEvent(eventType, identifier, source, 
oldValue, newValue);
 
                 // copy over if have
-                head.getMixee()
-                .ifPresent(mixeeAdapter->
-                    event.setMixee(mixeeAdapter.getPojo()));
+                mixee(head)
+                    .ifPresent(mixeeAdapter->
+                        event.setMixee(mixeeAdapter.getPojo()));
 
             }
 
@@ -345,9 +346,9 @@ public <S, T> CollectionDomainEvent<S, T> 
postEventForCollection(
             event = newCollectionDomainEvent(eventType, phase, identifier, 
source);
 
             // copy over if have
-            head.getMixee()
-            .ifPresent(mixeeAdapter->
-                event.setMixee(mixeeAdapter.getPojo()));
+            mixee(head)
+                .ifPresent(mixeeAdapter->
+                    event.setMixee(mixeeAdapter.getPojo()));
 
             event.setEventPhase(phase);
 
@@ -418,5 +419,14 @@ private static <T> T invokeConstructor(
                     "failed to invoke constructor %s", constructor);
         }
     }
+    
+    /**
+     * @return optionally the owner (mixee), based on whether the head 
corresponds to a mixin
+     */
+    private static Optional<ManagedObject> mixee(InteractionHead head) {
+        return head.isMixin()
+                ? Optional.of(head.owner())
+                : Optional.empty();
+    }
 
 }
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/actions/action/invocation/IdentifierUtil.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/actions/action/invocation/IdentifierUtil.java
deleted file mode 100644
index cb4066dc52f..00000000000
--- 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/actions/action/invocation/IdentifierUtil.java
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *        http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- */
-package org.apache.causeway.core.metamodel.facets.actions.action.invocation;
-
-import org.jspecify.annotations.Nullable;
-
-import org.apache.causeway.applib.Identifier;
-import org.apache.causeway.applib.id.LogicalType;
-import org.apache.causeway.applib.services.command.Command;
-import org.apache.causeway.commons.internal.base._Strings;
-import org.apache.causeway.commons.internal.exceptions._Exceptions;
-import org.apache.causeway.commons.io.TextUtils;
-import org.apache.causeway.core.metamodel.interactions.InteractionHead;
-import 
org.apache.causeway.core.metamodel.interactions.managed.ActionInteractionHead;
-import org.apache.causeway.core.metamodel.spec.ObjectSpecification;
-import org.apache.causeway.core.metamodel.spec.feature.ObjectAction;
-import org.apache.causeway.core.metamodel.spec.feature.ObjectMember;
-import org.apache.causeway.core.metamodel.spec.feature.OneToOneAssociation;
-import org.apache.causeway.core.metamodel.specloader.SpecificationLoader;
-
-import org.jspecify.annotations.NonNull;
-import lombok.SneakyThrows;
-import lombok.experimental.UtilityClass;
-
-/**
- * Factoring out the commonality between 
<tt>ActionInvocationFacetViaMethod</tt>
- * and <tt>BackgroundServiceDefault</tt>.
- */
-@UtilityClass
-public class IdentifierUtil {
-
-    public String targetClassNameFor(final ObjectSpecification spec) {
-        return _Strings.asNaturalName.apply(spec.getSingularName());
-    }
-
-    /**
-     * Recovers an {@link Identifier} for given {@code 
logicalMemberIdentifier}.
-     */
-    @SneakyThrows
-    public Identifier memberIdentifierFor(
-            final @NonNull SpecificationLoader specLoader,
-            final Identifier.@NonNull Type identifierType,
-            final @NonNull String logicalMemberIdentifier) {
-
-        var stringCutter = TextUtils.cutter(logicalMemberIdentifier);
-        var logicalTypeName = stringCutter
-                .keepBefore("#")
-                .getValue();
-        var memberId = stringCutter
-                .keepAfter("#")
-                .getValue();
-        var typeSpec = 
specLoader.specForLogicalTypeNameElseFail(logicalTypeName);
-        var logicalType = LogicalType.eager(typeSpec.getCorrespondingClass(), 
logicalTypeName);
-
-        if(identifierType.isAction()) {
-            return Identifier.actionIdentifier(logicalType, memberId);
-        }
-
-        if(identifierType.isProperty()) {
-            return Identifier.propertyIdentifier(logicalType, memberId);
-        }
-
-        if(identifierType.isCollection()) {
-            return Identifier.collectionIdentifier(logicalType, memberId);
-        }
-
-        throw _Exceptions.illegalArgument("unsupported identifier type %s 
(logicalMemberIdentifier=%s)",
-                identifierType, logicalMemberIdentifier);
-    }
-
-    /**
-     * Whether given command corresponds to given objectMember.
-     * <p>
-     * 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(interactionHead, objectMember)
-                    .equals(command.getLogicalMemberIdentifier());
-    }
-
-    public 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.owner().objSpec();
-                    return logicalMemberIdentifierFor(specificationOfMixee, 
objectActionOnMixee);
-                }
-            }
-            // we fall through to the declared case; this should suffice 
because this method is only called by code
-            // relating to commands, and contributed properties or collections 
don't emit commands.
-        }
-
-        return logicalMemberIdentifierForDeclaredMember(objectMember);
-    }
-
-    private String logicalMemberIdentifierFor(
-            final ObjectSpecification onType, final ObjectMember objectMember) 
{
-        final String logicalTypeName = onType.logicalTypeName();
-        final String localId = 
objectMember.getFeatureIdentifier().memberLogicalName();
-        return logicalTypeName + "#" + localId;
-    }
-
-    // -- HELPER
-
-    /**
-     * This assumes that the member is declared, ie is not a mixin.
-     */
-    private String logicalMemberIdentifierForDeclaredMember(final ObjectMember 
objectMember) {
-        if(objectMember instanceof ObjectAction) {
-            return 
logicalMemberIdentifierForDeclaredMember((ObjectAction)objectMember);
-        }
-        if(objectMember instanceof OneToOneAssociation) {
-            return 
logicalMemberIdentifierForDeclaredMember((OneToOneAssociation)objectMember);
-        }
-        throw new IllegalArgumentException(objectMember.getClass() + " is not 
supported");
-    }
-
-    /**
-     * This assumes that the member is declared, ie is not a mixin.
-     */
-    private String logicalMemberIdentifierForDeclaredMember(final ObjectAction 
objectAction) {
-        return logicalMemberIdentifierFor(objectAction.getDeclaringType(), 
objectAction);
-    }
-
-    /**
-     * This assumes that the member is declared, ie is not a mixin.
-     */
-    private String logicalMemberIdentifierForDeclaredMember(final 
OneToOneAssociation otoa) {
-        return logicalMemberIdentifierFor(otoa.getDeclaringType(), otoa);
-    }
-
-}
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/InteractionHead.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/InteractionHead.java
index eb502d3d4a0..f420f9eaa0c 100644
--- 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/InteractionHead.java
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/InteractionHead.java
@@ -18,14 +18,17 @@
  */
 package org.apache.causeway.core.metamodel.interactions;
 
-import java.util.Objects;
-import java.util.Optional;
-
 import org.jspecify.annotations.NonNull;
+import org.jspecify.annotations.Nullable;
 
+import org.apache.causeway.applib.services.command.Command;
 import org.apache.causeway.commons.internal.exceptions._Exceptions;
 import org.apache.causeway.core.metamodel.object.ManagedObject;
 import org.apache.causeway.core.metamodel.object.ManagedObjects;
+import org.apache.causeway.core.metamodel.spec.ObjectSpecification;
+import org.apache.causeway.core.metamodel.spec.feature.ObjectAction;
+import org.apache.causeway.core.metamodel.spec.feature.ObjectMember;
+import org.apache.causeway.core.metamodel.spec.feature.OneToOneAssociation;
 
 /**
  * Model that holds the objects involved with the interaction.
@@ -34,64 +37,90 @@
  * is represented.
  * @since 2.0
  */
-public interface InteractionHead {
-    /**
-     * The owning object of an interaction.
-     */
-    ManagedObject owner();
-
-    /**
-     * Typically equal to {@code owner}, except for mixins,
-     * where {@code target} is the mixin instance.
-     */
-    ManagedObject target();
-
+public record InteractionHead(
+        /**
+         * The owning (domain) object of an interaction.
+         */
+        ManagedObject owner,
+        /**
+         * Typically equal to {@code owner}, except for mixins,
+         * where {@code target} is the mixin instance.
+         */
+        ManagedObject target) {
+    
+    // -- FACTORIES
+    
     /** Regular case, when owner equals target. (no mixin) */
     public static InteractionHead regular(final ManagedObject owner) {
-        return new InteractionHeadRecord(owner, owner);
+        return new InteractionHead(owner, owner);
     }
 
     /** Mixin case, when target is a mixin for the owner. */
     public static InteractionHead mixin(final @NonNull ManagedObject owner, 
final @NonNull ManagedObject target) {
-        return new InteractionHeadRecord(owner, target);
+        return new InteractionHead(owner, target);
+    }
+    
+    // canonical constructor with consistency checks
+    public InteractionHead(
+        final ManagedObject owner,
+        final ManagedObject target) {
+        if(ManagedObjects.isSpecified(owner)
+            && owner.objSpec().getBeanSort().isMixin()) {
+            throw _Exceptions.unrecoverable("unexpected: owner is a mixin %s", 
owner);
+        }
+        if(ManagedObjects.isSpecified(target)
+            && target.objSpec().getBeanSort().isMixin()
+            && target.getPojo()==null) {
+            throw _Exceptions.unrecoverable("target not spec. %s", target);
+        }
+        this.owner = owner;
+        this.target = target;
     }
 
     /**
-     * as used by the domain event subsystem
-     * @return optionally the owner (mixee), based on whether the target is a 
mixin
+     * Whether given command corresponds to given objectMember
+     * by virtue of matching logical member identifiers.
      */
-    default Optional<ManagedObject> getMixee() {
-        return Objects.equals(owner(), target())
-                ? Optional.empty()
-                : Optional.of(owner());
+    public boolean isCommandForMember(
+            final @Nullable Command command,
+            final @Nullable ObjectMember objectMember) {
+        return command!=null
+                && objectMember!=null
+                && logicalMemberIdentifierFor(objectMember)
+                    .equals(command.getLogicalMemberIdentifier());
     }
-
-    // -- HELPER
-
+    
+    public String logicalMemberIdentifierFor(final ObjectMember objectMember) {
+        if (!objectMember.isMixedIn()
+                && objectMember instanceof ObjectAction objectAction
+                && objectAction.isDeclaredOnMixin()) {
+            // corner case when the objectMember is an ObjectActionDefault but 
corresponds to a mixin main  
+            return logicalMemberIdentifierFor(owner().objSpec(), 
+                objectMember.getProgrammingModel()
+                    .mixinNamingStrategy()
+                    
.memberId(objectAction.getFeatureIdentifier().logicalType().correspondingClass()));
+        }            
+        if(objectMember instanceof ObjectAction act) {
+            return logicalMemberIdentifierFor(act.getDeclaringType(), 
act.getFeatureIdentifier().memberLogicalName());
+        }
+        if(objectMember instanceof OneToOneAssociation prop) {
+            return logicalMemberIdentifierFor(prop.getDeclaringType(), 
prop.getFeatureIdentifier().memberLogicalName());
+        }
+        throw new IllegalArgumentException(objectMember.getClass() + " is not 
supported");    
+    }
+    
     /**
-     * Immutable implementation of {@link InteractionHead} with consistency 
checks.
+     * Whether this head corresponds to a mixin.
      */
-    record InteractionHeadRecord(
-        ManagedObject owner,
-        ManagedObject target) implements InteractionHead {
-
-        // canonical constructor with consistency checks
-        public InteractionHeadRecord(
-            final ManagedObject owner,
-            final ManagedObject target) {
-            if(ManagedObjects.isSpecified(owner)
-                && owner.objSpec().getBeanSort().isMixin()) {
-                throw _Exceptions.unrecoverable("unexpected: owner is a mixin 
%s", owner);
-            }
-            if(ManagedObjects.isSpecified(target)
-                && target.objSpec().getBeanSort().isMixin()
-                && target.getPojo()==null) {
-                throw _Exceptions.unrecoverable("target not spec. %s", target);
-            }
-            this.owner = owner;
-            this.target = target;
-        }
-
+    public boolean isMixin() {
+        return target.objSpec().isMixin();
+    }
+    
+    // -- HELPER
+    
+    private String logicalMemberIdentifierFor(final ObjectSpecification 
onType, final String memberId) {
+        return onType.logicalTypeName() + "#" + memberId;
     }
 
+    
 }
\ No newline at end of file
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/managed/ActionInteractionHead.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/managed/ActionInteractionHead.java
index 7a7f85ca2a7..e876d727485 100644
--- 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/managed/ActionInteractionHead.java
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/managed/ActionInteractionHead.java
@@ -29,29 +29,13 @@
 
 @Log4j2
 public record ActionInteractionHead(
-    @NonNull InteractionHeadRecord interactionHeadRecord,
-    @NonNull ObjectAction objectAction,
-    @NonNull MultiselectChoices multiselectChoices)
-implements InteractionHead, HasMetaModel<ObjectAction> {
+    @NonNull InteractionHead interactionHead,
+    @NonNull ObjectAction objectAction)
+implements HasMetaModel<ObjectAction> {
 
-    public static ActionInteractionHead of(
-            final @NonNull ObjectAction objectAction,
-            final @NonNull ManagedObject owner,
-            final @NonNull ManagedObject target) {
-        return new ActionInteractionHead(new InteractionHeadRecord(owner, 
target), objectAction, Can::empty);
-    }
-
-    public static ActionInteractionHead of(
-            final @NonNull ObjectAction objectAction,
-            final @NonNull ManagedObject owner,
-            final @NonNull ManagedObject target,
-            final @NonNull MultiselectChoices multiselectChoices) {
-        return new ActionInteractionHead(new InteractionHeadRecord(owner, 
target), objectAction, multiselectChoices);
-    }
-    
     @Override public ObjectAction getMetaModel() { return objectAction(); }
-    @Override public ManagedObject owner() { return 
interactionHeadRecord.owner(); }
-    @Override public ManagedObject target() { return 
interactionHeadRecord.target(); }
+    public ManagedObject owner() { return interactionHead.owner(); }
+    public ManagedObject target() { return interactionHead.target(); }
 
     /**
      * See step 1 'Fill in defaults' in
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/managed/ManagedAction.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/managed/ManagedAction.java
index 2ba1581c22d..5b21c9409c2 100644
--- 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/managed/ManagedAction.java
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/managed/ManagedAction.java
@@ -35,6 +35,7 @@
 import org.apache.causeway.commons.internal.base._NullSafe;
 import org.apache.causeway.core.metamodel.consent.InteractionInitiatedBy;
 import org.apache.causeway.core.metamodel.context.MetaModelContext;
+import org.apache.causeway.core.metamodel.interactions.InteractionHead;
 import org.apache.causeway.core.metamodel.object.ManagedObject;
 import org.apache.causeway.core.metamodel.object.ManagedObjects;
 import org.apache.causeway.core.metamodel.objectmanager.ObjectManager;
@@ -104,16 +105,20 @@ private ManagedAction(
     }
 
     //CAUSEWAY-2897 ... don't memoize the head, as owner might dynamically 
re-attach (when entity)
-    ActionInteractionHead interactionHead() {
+    InteractionHead interactionHead() {
         return action.interactionHead(getOwner());
     }
+    //CAUSEWAY-2897 ... don't memoize the head, as owner might dynamically 
re-attach (when entity)
+    ActionInteractionHead actionInteractionHead() {
+        return action.actionInteractionHead(getOwner());
+    }
 
     /**
      * @returns a new {@link ParameterNegotiationModel} that is associated 
with this managed-action;
      * parameters if any are initialized with their defaults (taking into 
account any supporting methods)
      */
     public ParameterNegotiationModel startParameterNegotiation() {
-        return interactionHead().defaults(this);
+        return actionInteractionHead().defaults(this);
     }
 
     @Override
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/managed/ParameterNegotiationModel.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/managed/ParameterNegotiationModel.java
index ea19b022518..ed4de33f9d3 100644
--- 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/managed/ParameterNegotiationModel.java
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/managed/ParameterNegotiationModel.java
@@ -41,11 +41,13 @@
 import org.apache.causeway.core.metamodel.consent.InteractionInitiatedBy;
 import org.apache.causeway.core.metamodel.consent.InteractionResult;
 import org.apache.causeway.core.metamodel.consent.Veto;
+import org.apache.causeway.core.metamodel.interactions.InteractionHead;
 import 
org.apache.causeway.core.metamodel.interactions.managed._BindingUtil.TargetFormat;
 import org.apache.causeway.core.metamodel.object.ManagedObject;
 import org.apache.causeway.core.metamodel.object.ManagedObjects;
 import org.apache.causeway.core.metamodel.object.MmAssertionUtils;
 import org.apache.causeway.core.metamodel.spec.ObjectSpecification;
+import org.apache.causeway.core.metamodel.spec.feature.ObjectAction;
 import org.apache.causeway.core.metamodel.spec.feature.ObjectActionParameter;
 
 import lombok.extern.log4j.Log4j2;
@@ -58,7 +60,7 @@
  *
  * @since 2.0.0
  */
-public class ParameterNegotiationModel {
+public final class ParameterNegotiationModel {
 
     public static ParameterNegotiationModel of(
             final @NonNull ManagedAction managedAction,
@@ -95,38 +97,46 @@ private ParameterNegotiationModel(
     }
 
     // -- ACTION SPECIFIC
+    
+    public ObjectAction act() {
+        return managedAction.getAction();
+    }
 
     public int getParamCount() {
         return paramModels.size();
     }
 
-    @NonNull public Can<ManagedObject> getParamValues() {
+    public Can<ManagedObject> getParamValues() {
         return paramModels.stream()
                 .map(ParameterModel::getValue)
                 .map(Bindable::getValue)
                 // guard against framework bugs
                 .peek(managedObj->Objects.requireNonNull(managedObj, ()->
                         String.format("Internal Error: Parameter value adapter 
must not be null in %s",
-                                
managedAction.getAction().getFeatureIdentifier())))
+                                act().getFeatureIdentifier())))
                 .collect(Can.toCan());
     }
 
-    @NonNull public ActionInteractionHead getHead() {
+    InteractionHead interactionHead() {
         return managedAction.interactionHead();
     }
+    
+    public ActionInteractionHead actionInteractionHead() {
+        return managedAction.actionInteractionHead();
+    }
 
-    @NonNull public ManagedObject getActionTarget() {
-        return getHead().target();
+    public ManagedObject getActionTarget() {
+        return interactionHead().target();
     }
 
-    @NonNull public Observable<String> getObservableActionValidation() {
+    public Observable<String> getObservableActionValidation() {
         return observableActionValidation;
     }
 
     /**
      * Whether validation feedback is activated. Activates once user attempts 
to 'submit' an action.
      */
-    @NonNull public Observable<Boolean> 
getObservableValidationFeedbackActive() {
+    public Observable<Boolean> getObservableValidationFeedbackActive() {
         return validationFeedbackActive;
     }
 
@@ -141,27 +151,27 @@ public void setParamValues(final @NonNull 
Can<ManagedObject> paramValues) {
 
     // -- PARAMETER SPECIFIC
 
-    @NonNull public Can<? extends ManagedParameter> getParamModels() {
+    public Can<? extends ManagedParameter> getParamModels() {
         return paramModels;
     }
 
-    @NonNull public ObjectActionParameter getParamMetamodel(final int paramNr) 
{
+    public ObjectActionParameter getParamMetamodel(final int paramNr) {
         return paramModels.getElseFail(paramNr).getMetaModel();
     }
 
-    @NonNull public Bindable<ManagedObject> getBindableParamValue(final int 
paramNr) {
+    public Bindable<ManagedObject> getBindableParamValue(final int paramNr) {
         return paramModels.getElseFail(paramNr).bindableParamValue();
     }
 
-    @NonNull public BooleanBindable getBindableParamValueDirtyFlag(final int 
paramNr) {
+    public BooleanBindable getBindableParamValueDirtyFlag(final int paramNr) {
         return paramModels.getElseFail(paramNr).bindableParamValueDirtyFlag();
     }
 
-    @NonNull public Observable<Can<ManagedObject>> 
getObservableParamChoices(final int paramNr) {
+    public Observable<Can<ManagedObject>> getObservableParamChoices(final int 
paramNr) {
         return paramModels.getElseFail(paramNr).observableParamChoices();
     }
 
-    @NonNull public Observable<String> getObservableParamValidation(final int 
paramNr) {
+    public Observable<String> getObservableParamValidation(final int paramNr) {
         return paramModels.getElseFail(paramNr).observableParamValidation();
     }
 
@@ -170,32 +180,33 @@ public void setParamValues(final @NonNull 
Can<ManagedObject> paramValues) {
      * (Ignoring the {@link ParameterModel#isValidationFeedbackActive()} flag.)
      * @apiNote introduced for [CAUSEWAY-3753] - not sure why required.
      */
-    @NonNull public String validateImmediately(final int paramIndex) {
-        return getHead().getMetaModel().getParameterByIndex(paramIndex)
+    public String validateImmediately(final int paramIndex) {
+        var actionInteractionHead = actionInteractionHead();
+        return 
actionInteractionHead.getMetaModel().getParameterByIndex(paramIndex)
                 .isValid(
-                        getHead(),
+                        actionInteractionHead.interactionHead(),
                         getParamValues(),
                         InteractionInitiatedBy.USER)
                 .getReasonAsString()
                 .orElse(null);
     }
 
-    @NonNull public Bindable<String> getBindableParamSearchArgument(final int 
paramNr) {
+    public Bindable<String> getBindableParamSearchArgument(final int paramNr) {
         return paramModels.getElseFail(paramNr).bindableParamSearchArgument();
     }
 
-    @NonNull public Observable<Consent> getObservableVisibilityConsent(final 
int paramNr) {
+    public Observable<Consent> getObservableVisibilityConsent(final int 
paramNr) {
         return paramModels.getElseFail(paramNr).observableVisibilityConsent();
     }
 
-    @NonNull public Observable<Consent> getObservableUsabilityConsent(final 
int paramNr) {
+    public Observable<Consent> getObservableUsabilityConsent(final int 
paramNr) {
         return paramModels.getElseFail(paramNr).observableUsabilityConsent();
     }
 
-    @NonNull public Consent getVisibilityConsent(final int paramNr) {
+    public Consent getVisibilityConsent(final int paramNr) {
         return getObservableVisibilityConsent(paramNr).getValue();
     }
-    @NonNull public Consent getUsabilityConsent(final int paramNr) {
+    public Consent getUsabilityConsent(final int paramNr) {
         return getObservableUsabilityConsent(paramNr).getValue();
     }
 
@@ -209,19 +220,16 @@ public MultiselectChoices getMultiselectChoices() {
 
     /** validates all, the action and each individual parameter */
     public Consent validateParameterSet() {
-        var head = this.getHead();
-        return head.getMetaModel().isArgumentSetValid(head, 
this.getParamValues(), InteractionInitiatedBy.USER);
+        return act().isArgumentSetValid(interactionHead(), 
this.getParamValues(), InteractionInitiatedBy.USER);
     }
 
     public Consent validateParameterSetForAction() {
-        var head = this.getHead();
-        return head.getMetaModel().isArgumentSetValidForAction(head, 
this.getParamValues(), InteractionInitiatedBy.USER);
+        return act().isArgumentSetValidForAction(interactionHead(), 
this.getParamValues(), InteractionInitiatedBy.USER);
     }
 
     public Can<Consent> validateParameterSetForParameters() {
-        var head = this.getHead();
-        return head.getMetaModel()
-                .isArgumentSetValidForParameters(head, this.getParamValues(), 
InteractionInitiatedBy.USER)
+        return act()
+                .isArgumentSetValidForParameters(interactionHead(), 
this.getParamValues(), InteractionInitiatedBy.USER)
                 .stream()
                 .map(InteractionResult::createConsent)
                 .collect(Can.toCan());
@@ -357,7 +365,7 @@ private ParameterModel(
             final int paramIndex,
             final @NonNull ParameterNegotiationModel negotiationModel,
             final @NonNull ManagedObject initialValue) {
-            this(paramIndex, 
negotiationModel.getHead().getMetaModel().getParameterByIndex(paramIndex), 
negotiationModel,
+            this(paramIndex, 
negotiationModel.act().getParameterByIndex(paramIndex), negotiationModel,
                 // bindableParamValue
                 _Bindables.forValue(initialValue),
                 // bindableParamValueDirtyFlag
@@ -431,12 +439,12 @@ private ParameterModel(
 
             this.observableVisibilityConsent = _Observables.lazy(()->
                 metaModel().isVisible(
-                        negotiationModel().getHead(),
+                        negotiationModel().interactionHead(),
                         negotiationModel().getParamValues(),
                         InteractionInitiatedBy.USER));
             this.observableUsabilityConsent = _Observables.lazy(()->
                 metaModel().isUsable(
-                        negotiationModel().getHead(),
+                        negotiationModel().interactionHead(),
                         negotiationModel().getParamValues(),
                         InteractionInitiatedBy.USER));
 
@@ -532,11 +540,9 @@ public Observable<Can<ManagedObject>> getChoices() {
         public Optional<InteractionVeto> checkUsability(@NonNull final 
Can<ManagedObject> params) {
 
             try {
-                var head = negotiationModel().getHead();
-
                 var usabilityConsent =
                     getMetaModel()
-                    .isUsable(head, params, InteractionInitiatedBy.USER);
+                    .isUsable(negotiationModel().interactionHead(), params, 
InteractionInitiatedBy.USER);
 
                 return usabilityConsent.isVetoed()
                     ? Optional.of(InteractionVeto.readonly(usabilityConsent))
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/object/MmDebugUtils.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/object/MmDebugUtils.java
index 0a21b6ce5cf..b8478673f9b 100644
--- 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/object/MmDebugUtils.java
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/object/MmDebugUtils.java
@@ -59,7 +59,7 @@ public static ParamUpdateData paramUpdateDataFor(
         var param = 
parameterNegotiationModel.getParamModels().getElseFail(parameterIndex);
         return ParamUpdateData.builder()
                 .index(parameterIndex)
-                
.action(parameterNegotiationModel.getHead().getMetaModel().getId())
+                .action(parameterNegotiationModel.act().getId())
                 .name(param.getFriendlyName())
                 .allParams(parameterNegotiationModel.getParamModels())
                 .build();
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/progmodel/ProgrammingModel.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/progmodel/ProgrammingModel.java
index 249ffffaa0e..b32ae334bbb 100644
--- 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/progmodel/ProgrammingModel.java
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/progmodel/ProgrammingModel.java
@@ -111,7 +111,17 @@ enum PostProcessingOrder {
 
     }
 
+    interface MixinNamingStrategy {
+        String memberId(Class<?> mixinClass);
+        String memberFriendlyName(Class<?> mixinClass);
+    }
+    
     // -- INTERFACE
+    
+    /**
+     * Naming strategy for mixed-in members. 
+     */
+    MixinNamingStrategy mixinNamingStrategy();
 
     <T extends FacetFactory> void addFactory(
             FacetProcessingOrder order,
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/schema/SchemaValueMarshallerAbstract.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/schema/SchemaValueMarshallerAbstract.java
index 6ecef20480e..3b9f51fd5ac 100644
--- 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/schema/SchemaValueMarshallerAbstract.java
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/schema/SchemaValueMarshallerAbstract.java
@@ -26,6 +26,7 @@
 
 import org.apache.causeway.applib.Identifier;
 import org.apache.causeway.applib.Identifier.Type;
+import org.apache.causeway.applib.id.LogicalType;
 import org.apache.causeway.applib.services.bookmark.Bookmark;
 import org.apache.causeway.applib.util.schema.CommonDtoUtils;
 import org.apache.causeway.applib.value.semantics.Converter;
@@ -35,9 +36,10 @@
 import org.apache.causeway.commons.collections.Cardinality;
 import org.apache.causeway.commons.internal.assertions._Assert;
 import org.apache.causeway.commons.internal.base._NullSafe;
+import org.apache.causeway.commons.internal.exceptions._Exceptions;
+import org.apache.causeway.commons.io.TextUtils;
 import org.apache.causeway.core.metamodel.context.HasMetaModelContext;
 import org.apache.causeway.core.metamodel.facetapi.FeatureType;
-import 
org.apache.causeway.core.metamodel.facets.actions.action.invocation.IdentifierUtil;
 import org.apache.causeway.core.metamodel.object.ManagedObject;
 import org.apache.causeway.core.metamodel.object.ProtoObject;
 import org.apache.causeway.core.metamodel.spec.ObjectSpecification;
@@ -45,6 +47,7 @@
 import org.apache.causeway.core.metamodel.spec.feature.ObjectActionParameter;
 import org.apache.causeway.core.metamodel.spec.feature.ObjectFeature;
 import org.apache.causeway.core.metamodel.spec.feature.OneToOneAssociation;
+import org.apache.causeway.core.metamodel.specloader.SpecificationLoader;
 import org.apache.causeway.schema.cmd.v2.ActionDto;
 import org.apache.causeway.schema.cmd.v2.ParamDto;
 import org.apache.causeway.schema.cmd.v2.PropertyDto;
@@ -55,6 +58,8 @@
 import org.apache.causeway.schema.common.v2.ValueWithTypeDto;
 import org.apache.causeway.schema.ixn.v2.ActionInvocationDto;
 
+import lombok.SneakyThrows;
+
 public abstract class SchemaValueMarshallerAbstract
 implements SchemaValueMarshaller, HasMetaModelContext {
 
@@ -183,21 +188,21 @@ public ParamDto recordParamNonScalar(
 
     @Override
     public final Identifier actionIdentifier(final @NonNull ActionDto 
actionDto) {
-        return IdentifierUtil.memberIdentifierFor(getSpecificationLoader(),
+        return memberIdentifierFor(getSpecificationLoader(),
                 Type.ACTION,
                 actionDto.getLogicalMemberIdentifier());
     }
 
     @Override
     public final Identifier actionIdentifier(final @NonNull 
ActionInvocationDto actionInvocationDto) {
-        return IdentifierUtil.memberIdentifierFor(getSpecificationLoader(),
+        return memberIdentifierFor(getSpecificationLoader(),
                 Type.ACTION,
                 actionInvocationDto.getLogicalMemberIdentifier());
     }
 
     @Override
     public final Identifier propertyIdentifier(final @NonNull PropertyDto 
propertyDto) {
-        return IdentifierUtil.memberIdentifierFor(getSpecificationLoader(),
+        return memberIdentifierFor(getSpecificationLoader(),
                 Type.PROPERTY,
                 propertyDto.getLogicalMemberIdentifier());
     }
@@ -232,6 +237,41 @@ public final ManagedObject recoverParameterFrom(
     }
 
     // -- HELPER
+    
+    /**
+     * Recovers an {@link Identifier} for given {@code 
logicalMemberIdentifier}.
+     */
+    @SneakyThrows
+    private static Identifier memberIdentifierFor(
+            final @NonNull SpecificationLoader specLoader,
+            final Identifier.@NonNull Type identifierType,
+            final @NonNull String logicalMemberIdentifier) {
+
+        var stringCutter = TextUtils.cutter(logicalMemberIdentifier);
+        var logicalTypeName = stringCutter
+                .keepBefore("#")
+                .getValue();
+        var memberId = stringCutter
+                .keepAfter("#")
+                .getValue();
+        var typeSpec = 
specLoader.specForLogicalTypeNameElseFail(logicalTypeName);
+        var logicalType = LogicalType.eager(typeSpec.getCorrespondingClass(), 
logicalTypeName);
+
+        if(identifierType.isAction()) {
+            return Identifier.actionIdentifier(logicalType, memberId);
+        }
+
+        if(identifierType.isProperty()) {
+            return Identifier.propertyIdentifier(logicalType, memberId);
+        }
+
+        if(identifierType.isCollection()) {
+            return Identifier.collectionIdentifier(logicalType, memberId);
+        }
+
+        throw _Exceptions.illegalArgument("unsupported identifier type %s 
(logicalMemberIdentifier=%s)",
+                identifierType, logicalMemberIdentifier);
+    }
 
     private <T> Context<T> newContext(
             final Class<T> valueCls,
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/feature/HasObjectAction.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/feature/HasObjectAction.java
index 79636a8e75c..2729dd71e34 100644
--- 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/feature/HasObjectAction.java
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/feature/HasObjectAction.java
@@ -34,7 +34,6 @@
 import org.apache.causeway.core.metamodel.facetapi.FacetHolder;
 import org.apache.causeway.core.metamodel.facetapi.FeatureType;
 import org.apache.causeway.core.metamodel.interactions.InteractionHead;
-import 
org.apache.causeway.core.metamodel.interactions.managed.ActionInteractionHead;
 import org.apache.causeway.core.metamodel.object.ManagedObject;
 import org.apache.causeway.core.metamodel.spec.ActionScope;
 import org.apache.causeway.core.metamodel.spec.ObjectSpecification;
@@ -149,7 +148,7 @@ default FacetHolder getFacetHolder() {
         final InteractionInitiatedBy interactionInitiatedBy) {
         return getObjectAction().isArgumentSetValidForAction(head, 
proposedArguments, interactionInitiatedBy);
     }
-    @Override default ActionInteractionHead interactionHead(@NonNull final 
ManagedObject actionOwner) {
+    @Override default InteractionHead interactionHead(@NonNull final 
ManagedObject actionOwner) {
         return getObjectAction().interactionHead(actionOwner);
     }
     @Override default int getParameterCount() {
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/feature/MixedInAction.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/feature/MixedInAction.java
index 398f099c2e0..33baa25af97 100644
--- 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/feature/MixedInAction.java
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/feature/MixedInAction.java
@@ -19,4 +19,5 @@
 package org.apache.causeway.core.metamodel.spec.feature;
 
 public interface MixedInAction extends ObjectAction, MixedInMember {
+    
 }
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/feature/MixedInMember.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/feature/MixedInMember.java
index 7a3d7553924..7eac06e4170 100644
--- 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/feature/MixedInMember.java
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/feature/MixedInMember.java
@@ -21,7 +21,7 @@
 import org.apache.causeway.core.metamodel.spec.ObjectSpecification;
 
 /**
- * Interface indicating an a contributed association or action.
+ * Interface indicating a contributed association or action.
  */
 public interface MixedInMember extends ObjectMember {
 
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/feature/ObjectAction.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/feature/ObjectAction.java
index 8c4b50f0f53..58c731c2243 100644
--- 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/feature/ObjectAction.java
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/feature/ObjectAction.java
@@ -177,7 +177,10 @@ Consent isArgumentSetValidForAction(
 
     // -- INTERACTION HEAD
 
-    ActionInteractionHead interactionHead(@NonNull ManagedObject actionOwner);
+    InteractionHead interactionHead(@NonNull ManagedObject actionOwner);
+    default ActionInteractionHead actionInteractionHead(@NonNull ManagedObject 
actionOwner) {
+        return new ActionInteractionHead(interactionHead(actionOwner), this);
+    }
 
     // -- Parameters (declarative)
 
@@ -407,13 +410,11 @@ public static String friendlyNameFor(
                 final @NonNull ObjectAction action,
                 final @NonNull InteractionHead head) {
 
-            var mixeeAdapter = head.getMixee().orElse(null);
-
-            if(mixeeAdapter != null) {
+            if(head.isMixin()) {
                 var mixinSpec = action.getDeclaringType();
-                var ownerSpec = mixeeAdapter.objSpec();
-                return ownerSpec.lookupMixedInMember(mixinSpec)
-                        
.map(mixedInMember->mixedInMember.getFriendlyName(mixeeAdapter))
+                var mixeeSpec = head.owner().objSpec();
+                return mixeeSpec.lookupMixedInMember(mixinSpec)
+                        
.map(mixedInMember->mixedInMember.getFriendlyName(head.owner()))
                         .orElseThrow(_Exceptions::unexpectedCodeReach);
             }
             return action.getFriendlyName(head::owner);
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/ObjectActionDefault.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/ObjectActionDefault.java
index e1b9f0c716d..6fe678790b9 100644
--- 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/ObjectActionDefault.java
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/ObjectActionDefault.java
@@ -53,7 +53,6 @@
 import 
org.apache.causeway.core.metamodel.facets.param.choices.ActionParameterChoicesFacet;
 import org.apache.causeway.core.metamodel.interactions.InteractionHead;
 import org.apache.causeway.core.metamodel.interactions.InteractionUtils;
-import 
org.apache.causeway.core.metamodel.interactions.managed.ActionInteractionHead;
 import 
org.apache.causeway.core.metamodel.interactions.use.ActionUsabilityContext;
 import org.apache.causeway.core.metamodel.interactions.use.UsabilityContext;
 import 
org.apache.causeway.core.metamodel.interactions.val.ActionValidityContext;
@@ -209,9 +208,8 @@ private static ActionScope getScope(final FacetHolder 
facetHolder) {
     }
 
     @Override
-    public ActionInteractionHead interactionHead(
-            final @NonNull ManagedObject actionOwner) {
-        return ActionInteractionHead.of(this, actionOwner, actionOwner);
+    public InteractionHead interactionHead(final @NonNull ManagedObject 
actionOwner) {
+        return InteractionHead.regular(actionOwner);
     }
 
     // -- Parameters
@@ -502,7 +500,7 @@ public CanVector<ManagedObject> getChoices(
 
                 var visibleChoices = paramFacet.getChoices(
                         paramSpec,
-                        interactionHead(target),
+                        actionInteractionHead(target),
                         emptyPendingArgs,
                         interactionInitiatedBy);
                 ObjectActionParameterAbstract.checkChoicesOrAutoCompleteType(
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/ObjectActionMixedIn.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/ObjectActionMixedIn.java
index b80fd26ee62..d51e8e526c1 100644
--- 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/ObjectActionMixedIn.java
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/ObjectActionMixedIn.java
@@ -20,6 +20,8 @@
 
 import java.util.Optional;
 
+import org.jspecify.annotations.NonNull;
+
 import org.apache.causeway.applib.Identifier;
 import org.apache.causeway.applib.annotation.Domain;
 import org.apache.causeway.applib.annotation.DomainObject;
@@ -34,14 +36,12 @@
 import org.apache.causeway.core.metamodel.facets.all.named.MemberNamedFacet;
 import 
org.apache.causeway.core.metamodel.facets.all.named.MemberNamedFacetForStaticMemberName;
 import org.apache.causeway.core.metamodel.interactions.InteractionHead;
-import 
org.apache.causeway.core.metamodel.interactions.managed.ActionInteractionHead;
 import org.apache.causeway.core.metamodel.object.ManagedObject;
 import org.apache.causeway.core.metamodel.spec.ObjectSpecification;
 import org.apache.causeway.core.metamodel.spec.feature.MixedInAction;
 import org.apache.causeway.core.metamodel.spec.feature.ObjectAction;
 
 import lombok.Getter;
-import org.jspecify.annotations.NonNull;
 import lombok.extern.log4j.Log4j2;
 
 @Log4j2
@@ -134,8 +134,8 @@ public ManagedObject realTargetAdapter(final ManagedObject 
targetAdapter) {
     }
 
     @Override
-    public ActionInteractionHead interactionHead(final @NonNull ManagedObject 
actionOwner) {
-        return ActionInteractionHead.of(this, actionOwner, 
mixinAdapterFor(actionOwner));
+    public InteractionHead interactionHead(final @NonNull ManagedObject 
actionOwner) {
+        return InteractionHead.mixin(actionOwner, 
mixinAdapterFor(actionOwner));
     }
 
     @Override
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/ObjectActionParameterAbstract.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/ObjectActionParameterAbstract.java
index 595f218094a..744b4aad4e3 100644
--- 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/ObjectActionParameterAbstract.java
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/ObjectActionParameterAbstract.java
@@ -231,7 +231,7 @@ public Can<ManagedObject> getChoices(
         }
 
         var visibleChoices = choicesFacet.getChoices(paramSpec,
-                pendingArgs.getHead(),
+                pendingArgs.actionInteractionHead(),
                 pendingArgs.getParamValues(),
                 interactionInitiatedBy);
         checkChoicesOrAutoCompleteType(specLoaderInternal(), visibleChoices, 
paramSpec);
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/ProgrammingModelDefault.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/ProgrammingModelDefault.java
index 96a6ca64cd9..40a398b1f18 100644
--- 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/ProgrammingModelDefault.java
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/ProgrammingModelDefault.java
@@ -99,6 +99,9 @@
 import org.apache.causeway.core.metamodel.progmodel.ProgrammingModelAbstract;
 import 
org.apache.causeway.core.metamodel.services.title.TitlesAndTranslationsValidator;
 
+import lombok.Getter;
+import lombok.experimental.Accessors;
+
 final class ProgrammingModelDefault
 extends ProgrammingModelAbstract {
 
@@ -117,8 +120,13 @@ final class ProgrammingModelDefault
         for (var metaModelRefiner : refiners) {
             metaModelRefiner.refineProgrammingModel(this);
         }
+        
+        this.mixinNamingStrategy = new _MixedInMemberNamingStrategy();    
     }
-
+    
+    @Getter(onMethod_={@Override}) @Accessors(fluent=true)
+    private final MixinNamingStrategy mixinNamingStrategy;
+    
     // -- HELPER
 
     private void addFacetFactories() {
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/_MixedInMemberNamingStrategy.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/_MixedInMemberNamingStrategy.java
index 3e1fb3be129..de0e20db8a0 100644
--- 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/_MixedInMemberNamingStrategy.java
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/_MixedInMemberNamingStrategy.java
@@ -20,45 +20,54 @@
 
 import java.util.Objects;
 
+import org.jspecify.annotations.NonNull;
+
 import org.apache.causeway.commons.internal.base._Strings;
+import org.apache.causeway.core.metamodel.progmodel.ProgrammingModel;
 
-import org.jspecify.annotations.NonNull;
-import lombok.experimental.UtilityClass;
+final class _MixedInMemberNamingStrategy implements 
ProgrammingModel.MixinNamingStrategy {
+    
+    @Override
+    public String memberId(Class<?> mixinClass) {
+        return mixinMemberId(mixinClass);
+    }
 
-@UtilityClass
-class _MixedInMemberNamingStrategy {
+    @Override
+    public String memberFriendlyName(Class<?> mixinClass) {
+        return mixinFriendlyName(mixinClass);
+    }
 
     /**
      * @param mixinActionAsRegular - first pass MM introspection produces 
regular ObjectAction instances
      *              for mixin main methods
      */
-    String mixinFriendlyName(final @NonNull ObjectActionDefault 
mixinActionAsRegular) {
-        return mixinFriendlyName(mixinClassSimpleName(mixinActionAsRegular));
+    static String mixinFriendlyName(final @NonNull ObjectActionDefault 
mixinActionAsRegular) {
+        return mixinFriendlyName(mixinClass(mixinActionAsRegular));
     }
 
-    String mixinFriendlyName(final @NonNull String mixinClassSimpleName) {
-        return 
_Strings.asCamelCase.andThen(_Strings.asNaturalName).apply(lastWord(mixinClassSimpleName));
+    static String mixinFriendlyName(final @NonNull Class<?> mixinClass) {
+        return 
_Strings.asCamelCase.andThen(_Strings.asNaturalName).apply(lastWord(mixinClass.getSimpleName()));
     }
 
     /**
      * @param mixinActionAsRegular - first pass MM introspection produces 
regular ObjectAction instances
      *              for mixin main methods
      */
-    String mixinMemberId(final @NonNull ObjectActionDefault 
mixinActionAsRegular) {
-        return mixinMemberId(mixinClassSimpleName(mixinActionAsRegular));
+    static String mixinMemberId(final @NonNull ObjectActionDefault 
mixinActionAsRegular) {
+        return mixinMemberId(mixinClass(mixinActionAsRegular));
     }
 
-    String mixinMemberId(final @NonNull String mixinClassSimpleName) {
-        return _Strings.decapitalize(lastWord(mixinClassSimpleName));
+    static String mixinMemberId(final @NonNull Class<?> mixinClass) {
+        return _Strings.decapitalize(lastWord(mixinClass.getSimpleName()));
     }
 
     // -- HELPER
 
-    private String mixinClassSimpleName(final ObjectActionDefault 
mixinActionAsRegular) {
-        return 
mixinActionAsRegular.getFeatureIdentifier().logicalType().correspondingClass().getSimpleName();
+    private static Class<?> mixinClass(final ObjectActionDefault 
mixinActionAsRegular) {
+        return 
mixinActionAsRegular.getFeatureIdentifier().logicalType().correspondingClass();
     }
 
-    private String lastWord(final String mixinClassSimpleName) {
+    private static String lastWord(final String mixinClassSimpleName) {
         final String deriveFromUnderscore = lastToken(mixinClassSimpleName, 
"_");
         if(!Objects.equals(mixinClassSimpleName, deriveFromUnderscore)) {
             return deriveFromUnderscore;
@@ -70,14 +79,14 @@ private String lastWord(final String mixinClassSimpleName) {
         return mixinClassSimpleName;
     }
 
-    private String lastToken(final String singularName, final String 
separator) {
+    private static String lastToken(final String singularName, final String 
separator) {
         final int indexOfSeparator = singularName.lastIndexOf(separator);
         return occursNotAtEnd(singularName, indexOfSeparator)
                 ? singularName.substring(indexOfSeparator + 1)
                 : singularName;
     }
 
-    private boolean occursNotAtEnd(final String singularName, final int 
indexOfUnderscore) {
+    private static boolean occursNotAtEnd(final String singularName, final int 
indexOfUnderscore) {
         return indexOfUnderscore != -1
                 && indexOfUnderscore != singularName.length() - 1;
     }
diff --git 
a/core/metamodel/src/test/java/org/apache/causeway/core/metamodel/facets/AbstractTestWithMetaModelContext.java
 
b/core/metamodel/src/test/java/org/apache/causeway/core/metamodel/facets/AbstractTestWithMetaModelContext.java
index 52bcde45b78..41823f5d8c4 100644
--- 
a/core/metamodel/src/test/java/org/apache/causeway/core/metamodel/facets/AbstractTestWithMetaModelContext.java
+++ 
b/core/metamodel/src/test/java/org/apache/causeway/core/metamodel/facets/AbstractTestWithMetaModelContext.java
@@ -20,6 +20,7 @@
 
 import java.util.function.BiConsumer;
 
+import org.apache.causeway.commons.internal.exceptions._Exceptions;
 import org.apache.causeway.core.metamodel._testing.MetaModelContext_forTesting;
 import org.apache.causeway.core.metamodel.context.HasMetaModelContext;
 import org.apache.causeway.core.metamodel.context.MetaModelContext;
@@ -49,6 +50,9 @@ protected final void setupWithProgrammingModel(final 
BiConsumer<MetaModelContext
                 .programmingModelFactory((mmc, refiners)->{
                     var progModel = new ProgrammingModelAbstract(mmc) {
                         @Override protected void assertNotInitialized(){}
+                        @Override public MixinNamingStrategy 
mixinNamingStrategy() {
+                            throw _Exceptions.notImplemented();
+                        }
                     };
                     factory.accept(mmc, progModel);
                     progModel.init(new ProgrammingModelInitFilterDefault());
diff --git 
a/core/metamodel/src/test/java/org/apache/causeway/core/metamodel/facets/object/ViewModelSemanticCheckingFacetFactoryTest.java
 
b/core/metamodel/src/test/java/org/apache/causeway/core/metamodel/facets/object/ViewModelSemanticCheckingFacetFactoryTest.java
index de85e2545e5..6a852939f9f 100644
--- 
a/core/metamodel/src/test/java/org/apache/causeway/core/metamodel/facets/object/ViewModelSemanticCheckingFacetFactoryTest.java
+++ 
b/core/metamodel/src/test/java/org/apache/causeway/core/metamodel/facets/object/ViewModelSemanticCheckingFacetFactoryTest.java
@@ -26,6 +26,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import org.apache.causeway.applib.services.inject.ServiceInjector;
+import org.apache.causeway.commons.internal.exceptions._Exceptions;
 import org.apache.causeway.core.config.CausewayConfiguration;
 import org.apache.causeway.core.metamodel._testing.MetaModelContext_forTesting;
 import org.apache.causeway.core.metamodel.context.MetaModelContext;
@@ -50,7 +51,11 @@ public void setUp() throws Exception {
 
         metaModelContext = MetaModelContext_forTesting.builder()
                 .configuration(configuration)
-                .programmingModelFactory((mmc, refiners)->new 
ProgrammingModelAbstract(mmc) {})
+                .programmingModelFactory((mmc, refiners)->new 
ProgrammingModelAbstract(mmc) {
+                    @Override public MixinNamingStrategy mixinNamingStrategy() 
{
+                        throw _Exceptions.notImplemented();
+                    }
+                })
                 .build();
 
         facetFactory = new 
ViewModelSemanticCheckingFacetFactory(metaModelContext);
diff --git 
a/core/metamodel/src/test/java/org/apache/causeway/core/metamodel/spec/impl/MixedInMemberNamingStrategyTest.java
 
b/core/metamodel/src/test/java/org/apache/causeway/core/metamodel/spec/impl/MixedInMemberNamingStrategyTest.java
index 1da0fe8a639..52a606fb573 100644
--- 
a/core/metamodel/src/test/java/org/apache/causeway/core/metamodel/spec/impl/MixedInMemberNamingStrategyTest.java
+++ 
b/core/metamodel/src/test/java/org/apache/causeway/core/metamodel/spec/impl/MixedInMemberNamingStrategyTest.java
@@ -28,33 +28,45 @@
 
 class MixedInMemberNamingStrategyTest {
 
+    static class Customer_placeOrder {}
+    static class abc_ {}
+    static class lock {}
+    static class ApplicationUser_default_lock {}
+    static class Customer {
+        class placeOrder {}
+    }
+    
+    
     @RequiredArgsConstructor
     enum Scenario {
-        SINGLE_UNDERSCORE("Customer_placeOrder", "placeOrder", "Place Order"),
-        SINGLE_DOLLAR("Customer$placeOrder", "placeOrder", "Place Order"),
+        SINGLE_UNDERSCORE(Customer_placeOrder.class, "placeOrder", "Place 
Order"),
+        SINGLE_DOLLAR(Customer.placeOrder.class, "placeOrder", "Place Order"),
         //EXACTLY_UNDERSCORE("_", "_", "_"), //TODO this should throw instead
-        ENDS_WITH_UNDERSCORE("abc_", "abc_", "Abc"),
-        HAS_NO_UNDERSCORE("lock", "lock", "Lock"),
-        CONTAINS_MORE_THAN_ONE_UNDERSCORE("ApplicationUser_default_lock", 
"lock", "Lock")
+        ENDS_WITH_UNDERSCORE(abc_.class, "abc_", "Abc"),
+        HAS_NO_UNDERSCORE(lock.class, "lock", "Lock"),
+        CONTAINS_MORE_THAN_ONE_UNDERSCORE(ApplicationUser_default_lock.class, 
"lock", "Lock")
         ;
 
-        final String mixinClassSimpleName;
+        final Class<?> mixinClass;
         final String expectedMemberId;
         final String expectedFriendlyName;
 
         void verify() {
+            
+            System.err.printf("%s%n", mixinClass.getCanonicalName());
+            
             assertThat(
-                    
_MixedInMemberNamingStrategy.mixinMemberId(mixinClassSimpleName),
+                    _MixedInMemberNamingStrategy.mixinMemberId(mixinClass),
                     is(expectedMemberId));
 
             assertThat(
-                    
_MixedInMemberNamingStrategy.mixinFriendlyName(mixinClassSimpleName),
+                    _MixedInMemberNamingStrategy.mixinFriendlyName(mixinClass),
                     is(expectedFriendlyName));
         }
 
         @Override
         public String toString() {
-            return String.format("%s->%s (%s)", mixinClassSimpleName, 
expectedMemberId, name());
+            return String.format("%s->%s (%s)", mixinClass, expectedMemberId, 
name());
         }
 
     }
diff --git 
a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/command/CommandDtoFactoryDefault.java
 
b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/command/CommandDtoFactoryDefault.java
index 2b7038483a0..2d0343d0521 100644
--- 
a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/command/CommandDtoFactoryDefault.java
+++ 
b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/command/CommandDtoFactoryDefault.java
@@ -35,7 +35,6 @@
 import org.apache.causeway.commons.collections.Can;
 import org.apache.causeway.core.config.CausewayConfiguration;
 import org.apache.causeway.core.metamodel.facetapi.FeatureType;
-import 
org.apache.causeway.core.metamodel.facets.actions.action.invocation.IdentifierUtil;
 import org.apache.causeway.core.metamodel.interactions.InteractionHead;
 import org.apache.causeway.core.metamodel.object.ManagedObject;
 import org.apache.causeway.core.metamodel.object.ManagedObjects;
@@ -117,7 +116,7 @@ public void addActionArgs(
             final ActionDto actionDto,
             final Can<ManagedObject> argAdapters) {
 
-        
actionDto.setLogicalMemberIdentifier(IdentifierUtil.logicalMemberIdentifierFor(head,
 objectAction));
+        
actionDto.setLogicalMemberIdentifier(head.logicalMemberIdentifierFor(objectAction));
 
         var actionParameters = objectAction.getParameters();
         for (int paramNum = 0; paramNum < actionParameters.size(); paramNum++) 
{
@@ -150,12 +149,12 @@ public void addActionArgs(
 
     @Override
     public void addPropertyValue(
-            final InteractionHead interactionHead,
+            final InteractionHead head,
             final OneToOneAssociation property,
             final PropertyDto propertyDto,
             final ManagedObject valueAdapter) {
 
-        
propertyDto.setLogicalMemberIdentifier(IdentifierUtil.logicalMemberIdentifierFor(interactionHead,
 property));
+        
propertyDto.setLogicalMemberIdentifier(head.logicalMemberIdentifierFor(property));
 
         valueMarshaller.recordPropertyValue(propertyDto, property, 
valueAdapter);
     }
diff --git 
a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/executor/MemberExecutorServiceDefault.java
 
b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/executor/MemberExecutorServiceDefault.java
index 070830492b7..0b5d1fa6857 100644
--- 
a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/executor/MemberExecutorServiceDefault.java
+++ 
b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/executor/MemberExecutorServiceDefault.java
@@ -25,7 +25,7 @@
 import jakarta.inject.Named;
 import jakarta.inject.Provider;
 
-import org.apache.causeway.core.metamodel.services.deadlock.DeadlockRecognizer;
+import org.jspecify.annotations.NonNull;
 
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.stereotype.Service;
@@ -54,7 +54,6 @@
 import org.apache.causeway.core.metamodel.execution.MemberExecutorService;
 import org.apache.causeway.core.metamodel.execution.PropertyModifier;
 import org.apache.causeway.core.metamodel.facetapi.FacetHolder;
-import 
org.apache.causeway.core.metamodel.facets.actions.action.invocation.IdentifierUtil;
 import 
org.apache.causeway.core.metamodel.facets.members.publish.execution.ExecutionPublishingFacet;
 import org.apache.causeway.core.metamodel.interactions.InteractionHead;
 import org.apache.causeway.core.metamodel.object.ManagedObject;
@@ -64,6 +63,7 @@
 import org.apache.causeway.core.metamodel.object.MmVisibilityUtils;
 import org.apache.causeway.core.metamodel.object.PackedManagedObject;
 import org.apache.causeway.core.metamodel.objectmanager.ObjectManager;
+import org.apache.causeway.core.metamodel.services.deadlock.DeadlockRecognizer;
 import 
org.apache.causeway.core.metamodel.services.events.MetamodelEventService;
 import org.apache.causeway.core.metamodel.services.ixn.InteractionDtoFactory;
 import org.apache.causeway.core.metamodel.services.publishing.CommandPublisher;
@@ -76,7 +76,6 @@
 import static 
org.apache.causeway.core.metamodel.facets.members.publish.command.CommandPublishingFacet.isPublishingEnabled;
 
 import lombok.Getter;
-import org.jspecify.annotations.NonNull;
 import lombok.RequiredArgsConstructor;
 import lombok.SneakyThrows;
 import lombok.extern.log4j.Log4j2;
@@ -363,7 +362,7 @@ void prepareCommandForPublishing(
             final @NonNull ObjectMember objectMember,
             final @NonNull FacetHolder facetHolder) {
 
-        if(IdentifierUtil.isCommandForMember(command, interactionHead, 
objectMember)
+        if(interactionHead.isCommandForMember(command, objectMember)
                 && isPublishingEnabled(facetHolder)) {
             
command.updater().setPublishingPhase(Command.CommandPublishingPhase.READY);
         }
diff --git 
a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/wrapper/handlers/DomainObjectInvocationHandler.java
 
b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/wrapper/handlers/DomainObjectInvocationHandler.java
index 797c4ce0242..a9bbfca8e77 100644
--- 
a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/wrapper/handlers/DomainObjectInvocationHandler.java
+++ 
b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/wrapper/handlers/DomainObjectInvocationHandler.java
@@ -48,7 +48,7 @@
 import org.apache.causeway.core.metamodel.consent.InteractionResult;
 import org.apache.causeway.core.metamodel.facets.ImperativeFacet;
 import org.apache.causeway.core.metamodel.facets.ImperativeFacet.Intent;
-import 
org.apache.causeway.core.metamodel.interactions.managed.ActionInteractionHead;
+import org.apache.causeway.core.metamodel.interactions.InteractionHead;
 import org.apache.causeway.core.metamodel.object.ManagedObject;
 import org.apache.causeway.core.metamodel.object.MmAssertionUtils;
 import org.apache.causeway.core.metamodel.object.MmEntityUtils;
@@ -423,7 +423,7 @@ private Object handleActionMethod(
 
     private void checkValidity(
             final WrapperInvocation wrapperInvocation,
-            final ActionInteractionHead head,
+            final InteractionHead head,
             final ObjectAction objectAction,
             final Can<ManagedObject> argAdapters) {
 
diff --git 
a/persistence/jdo/metamodel/src/test/java/org/apache/causeway/persistence/jdo/metamodel/facets/object/version/JdoVersionAnnotationFacetFactoryTest_validate.java
 
b/persistence/jdo/metamodel/src/test/java/org/apache/causeway/persistence/jdo/metamodel/facets/object/version/JdoVersionAnnotationFacetFactoryTest_validate.java
index 2738e7ada8b..d9f549171ec 100644
--- 
a/persistence/jdo/metamodel/src/test/java/org/apache/causeway/persistence/jdo/metamodel/facets/object/version/JdoVersionAnnotationFacetFactoryTest_validate.java
+++ 
b/persistence/jdo/metamodel/src/test/java/org/apache/causeway/persistence/jdo/metamodel/facets/object/version/JdoVersionAnnotationFacetFactoryTest_validate.java
@@ -28,6 +28,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
+import org.apache.causeway.commons.internal.exceptions._Exceptions;
 import org.apache.causeway.core.config.CausewayConfiguration;
 import org.apache.causeway.core.metamodel._testing.MetaModelContext_forTesting;
 import 
org.apache.causeway.core.metamodel.progmodel.ProgrammingModel.FacetProcessingOrder;
@@ -48,7 +49,11 @@ void setUp() throws Exception {
         .builder()
         .programmingModelFactory((mmc, refiners)->{
 
-            var programmingModel = new ProgrammingModelAbstract(mmc) {};
+            var programmingModel = new ProgrammingModelAbstract(mmc) {
+                @Override public MixinNamingStrategy mixinNamingStrategy() {
+                    throw _Exceptions.notImplemented();
+                }
+            };
 
             var facetFactory = new JdoVersionAnnotationFacetFactory(
                     metaModelContext,
diff --git 
a/regressiontests/interact/src/test/java/org/apache/causeway/testdomain/interact/ActionInteractionTest.java
 
b/regressiontests/interact/src/test/java/org/apache/causeway/testdomain/interact/ActionInteractionTest.java
index 73ed79f980b..d9f6583f0c5 100644
--- 
a/regressiontests/interact/src/test/java/org/apache/causeway/testdomain/interact/ActionInteractionTest.java
+++ 
b/regressiontests/interact/src/test/java/org/apache/causeway/testdomain/interact/ActionInteractionTest.java
@@ -35,7 +35,6 @@
 import org.apache.causeway.commons.collections.Can;
 import org.apache.causeway.commons.internal.collections._Lists;
 import org.apache.causeway.core.config.presets.CausewayPresets;
-import 
org.apache.causeway.core.metamodel.facets.actions.action.invocation.IdentifierUtil;
 import org.apache.causeway.core.metamodel.interactions.InteractionHead;
 import org.apache.causeway.testdomain.conf.Configuration_headless;
 import 
org.apache.causeway.testdomain.model.interaction.Configuration_usingInteractionDomain;
@@ -132,7 +131,7 @@ void whenEnabled_shouldAllowInvocation() {
         // test feature-identifier to command matching ...
         var act = tester.getActionMetaModelElseFail();
         InteractionHead head = 
tester.getActionMetaModelElseFail().interactionHead(tester.getActionOwnerElseFail());
-        assertTrue(IdentifierUtil.isCommandForMember(command, head, act));
+        assertTrue(head.isCommandForMember(command, act));
     }
 
     @Test
@@ -179,7 +178,7 @@ void mixinWithParams_shouldProduceCorrectResult() throws 
Throwable {
         // test feature-identifier to command matching ...
         var act = tester.getActionMetaModelElseFail();
         InteractionHead head = 
tester.getActionMetaModelElseFail().interactionHead(tester.getActionOwnerElseFail());
-        assertTrue(IdentifierUtil.isCommandForMember(command, head, act));
+        assertTrue(head.isCommandForMember(command, act));
     }
 
     @Test
diff --git 
a/viewers/commons/model/src/main/java/org/apache/causeway/viewer/commons/model/attrib/HasUiParameter.java
 
b/viewers/commons/model/src/main/java/org/apache/causeway/viewer/commons/model/attrib/HasUiParameter.java
index 5240abee76f..e4e51fe2e24 100644
--- 
a/viewers/commons/model/src/main/java/org/apache/causeway/viewer/commons/model/attrib/HasUiParameter.java
+++ 
b/viewers/commons/model/src/main/java/org/apache/causeway/viewer/commons/model/attrib/HasUiParameter.java
@@ -149,7 +149,7 @@ default Optional<InteractionVeto> disabledReason() {
 
     @Override
     default ActionInteractionHead getPendingParamHead() {
-        return 
getMetaModel().getAction().interactionHead(getUiParameter().getOwner());
+        return 
getMetaModel().getAction().actionInteractionHead(getUiParameter().getOwner());
     }
 
 }
diff --git 
a/viewers/commons/model/src/main/java/org/apache/causeway/viewer/commons/model/attrib/UiParameter.java
 
b/viewers/commons/model/src/main/java/org/apache/causeway/viewer/commons/model/attrib/UiParameter.java
index 24ca39a3f07..a1621a4651d 100644
--- 
a/viewers/commons/model/src/main/java/org/apache/causeway/viewer/commons/model/attrib/UiParameter.java
+++ 
b/viewers/commons/model/src/main/java/org/apache/causeway/viewer/commons/model/attrib/UiParameter.java
@@ -143,7 +143,7 @@ default String getIdentifier() {
     }
 
     default ActionInteractionHead getPendingParamHead() {
-        return getMetaModel().getAction().interactionHead(getOwner());
+        return getMetaModel().getAction().actionInteractionHead(getOwner());
     }
 
 }
diff --git 
a/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/_DomainResourceHelper.java
 
b/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/_DomainResourceHelper.java
index a8137bdbe81..3143e281aa6 100644
--- 
a/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/_DomainResourceHelper.java
+++ 
b/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/_DomainResourceHelper.java
@@ -228,11 +228,10 @@ private Response invokeAction(
 
             // parse parameters ...
 
-            var action = pendingArgs.getHead().getMetaModel();
             var vetoCount = new LongAdder();
 
             var paramsOrVetos = ObjectActionArgHelper
-                    .parseArguments(resourceContext, action, arguments);
+                    .parseArguments(resourceContext, pendingArgs.act(), 
arguments);
 
             pendingArgs.getParamModels().zip(paramsOrVetos, (managedParam, 
paramOrVeto)->{
 

Reply via email to