You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@causeway.apache.org by ah...@apache.org on 2023/03/31 18:28:11 UTC

[causeway] branch master updated: CAUSEWAY-3401: refactor disabledReason string type into new VetoReason type

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 4aeb2f1add CAUSEWAY-3401: refactor disabledReason string type into new VetoReason type
4aeb2f1add is described below

commit 4aeb2f1add232c075416df4567ba28480471b785
Author: Andi Huber <ah...@apache.org>
AuthorDate: Fri Mar 31 20:28:04 2023 +0200

    CAUSEWAY-3401: refactor disabledReason string type into new VetoReason
    type
    
    - so we are able to distinguish between inferred and explicitly given
    reasons within the framework
---
 .../causeway/core/metamodel/consent/Consent.java   | 25 ++++++++++++++
 .../invocation/ActionDomainEventFacetAbstract.java | 16 ++++++---
 .../facets/members/disabled/DisabledFacet.java     | 24 +++++---------
 .../members/disabled/DisabledFacetAbstract.java    | 25 +++++++-------
 .../disabled/DisabledFacetForContributee.java      |  3 +-
 .../method/DisableForContextFacetNone.java         |  9 +++--
 .../method/DisableForContextFacetViaMethod.java    | 22 +++++++------
 .../choices/ChoicesFacetFromBoundedAbstract.java   |  9 +++--
 .../disabled/DisabledObjectFacetAbstract.java      |  9 +++--
 .../method/DisabledObjectFacetViaMethod.java       | 20 +++++++-----
 .../ImmutableFacetForDomainObjectAnnotation.java   |  6 ++--
 ...FacetForDomainObjectAnnotationAsConfigured.java |  3 +-
 .../editing/ImmutableFacetFromConfiguration.java   |  6 ++--
 .../facets/object/immutable/ImmutableFacet.java    |  5 ++-
 .../object/immutable/ImmutableFacetAbstract.java   | 38 ++++++++++++++--------
 .../value/ImmutableFacetViaValueSemantics.java     |  3 +-
 .../disable/ActionParameterDisabledFacet.java      |  7 ++--
 .../ActionParameterDisabledFacetAbstract.java      |  7 ++--
 .../ActionParameterDisabledFacetViaMethod.java     | 19 ++++++-----
 .../DisabledFacetOnPropertyFromImmutable.java      | 15 +++++----
 .../DisabledFacetForPropertyAnnotation.java        |  7 ++--
 ...acetForPropertyAnnotationInvertedSemantics.java |  5 +--
 .../modify/PropertyDomainEventFacetAbstract.java   | 20 ++++++++----
 .../interactions/DisablingInteractionAdvisor.java  |  5 ++-
 .../metamodel/interactions/InteractionUtils.java   | 17 +++++-----
 .../authorization/AuthorizationFacetAbstract.java  |  9 +++--
 .../specimpl/OneToManyAssociationMixedIn.java      |  3 +-
 .../specimpl/OneToOneAssociationMixedIn.java       |  3 +-
 .../PropertyAnnotationFacetFactoryTest.java        |  3 +-
 .../facets/TenantedAuthorizationFacetDefault.java  | 30 +++++++++--------
 .../DisabledFacetFromJdoPrimaryKeyAnnotation.java  |  3 +-
 31 files changed, 232 insertions(+), 144 deletions(-)

diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/consent/Consent.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/consent/Consent.java
index 6a66d33d8e..1328d4b381 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/consent/Consent.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/consent/Consent.java
@@ -18,8 +18,33 @@
  */
 package org.apache.causeway.core.metamodel.consent;
 
+import java.io.Serializable;
+import java.util.Optional;
+
+import lombok.NonNull;
+import lombok.experimental.Accessors;
+
 public interface Consent {
 
+    //XXX record candidate
+    @lombok.Value @Accessors(fluent=true)
+    public static class VetoReason implements Serializable {
+        private static final long serialVersionUID = 1L;
+        /** Reason inferred by the framework or explicitly given otherwise. */
+        private final boolean inferred;
+        private final @NonNull String string;
+        public static VetoReason inferred(final String reason) {
+            return new VetoReason(true, reason);
+        }
+        public static VetoReason explicit(final String reason) {
+            return new VetoReason(false, reason);
+        }
+        public Optional<VetoReason> toOptional() {
+            return Optional.of(this);
+        }
+    }
+
+
     /**
      * Returns true if this object is giving permission.
      */
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/actions/action/invocation/ActionDomainEventFacetAbstract.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/actions/action/invocation/ActionDomainEventFacetAbstract.java
index 460e44d472..eb3e5a8ff0 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/actions/action/invocation/ActionDomainEventFacetAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/actions/action/invocation/ActionDomainEventFacetAbstract.java
@@ -18,6 +18,8 @@
  */
 package org.apache.causeway.core.metamodel.facets.actions.action.invocation;
 
+import java.util.Optional;
+
 import org.apache.causeway.applib.events.domain.AbstractDomainEvent;
 import org.apache.causeway.applib.events.domain.ActionDomainEvent;
 import org.apache.causeway.applib.services.i18n.TranslatableString;
@@ -25,6 +27,7 @@ import org.apache.causeway.applib.services.i18n.TranslationContext;
 import org.apache.causeway.applib.services.i18n.TranslationService;
 import org.apache.causeway.commons.collections.Can;
 import org.apache.causeway.commons.internal.assertions._Assert;
+import org.apache.causeway.core.metamodel.consent.Consent.VetoReason;
 import org.apache.causeway.core.metamodel.facetapi.FacetHolder;
 import org.apache.causeway.core.metamodel.facets.DomainEventHelper;
 import org.apache.causeway.core.metamodel.facets.SingleClassValueFacetAbstract;
@@ -87,7 +90,7 @@ implements ActionDomainEventFacet {
     }
 
     @Override
-    public String disables(final UsabilityContext ic) {
+    public Optional<VetoReason> disables(final UsabilityContext ic) {
 
         final ActionDomainEvent<?> event =
                 domainEventHelper.postEventForAction(
@@ -102,12 +105,15 @@ implements ActionDomainEventFacet {
                         null);
         if (event != null && event.isDisabled()) {
             final TranslatableString reasonTranslatable = event.getDisabledReasonTranslatable();
-            if(reasonTranslatable != null) {
-                return reasonTranslatable.translate(translationService, translationContext);
+            final String reasonString = reasonTranslatable != null
+                    ? reasonTranslatable.translate(translationService, translationContext)
+                    : event.getDisabledReason();
+
+            if(reasonString!=null) {
+                return VetoReason.explicit(reasonString).toOptional();
             }
-            return event.getDisabledReason();
         }
-        return null;
+        return Optional.empty();
     }
 
     @Override
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/members/disabled/DisabledFacet.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/members/disabled/DisabledFacet.java
index f77ade72ba..2c3922835d 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/members/disabled/DisabledFacet.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/members/disabled/DisabledFacet.java
@@ -18,8 +18,9 @@
  */
 package org.apache.causeway.core.metamodel.facets.members.disabled;
 
-import org.springframework.lang.Nullable;
+import java.util.Optional;
 
+import org.apache.causeway.core.metamodel.consent.Consent.VetoReason;
 import org.apache.causeway.core.metamodel.facetapi.FacetHolder;
 import org.apache.causeway.core.metamodel.facets.WhereValueFacet;
 import org.apache.causeway.core.metamodel.interactions.DisablingInteractionAdvisor;
@@ -38,33 +39,24 @@ public interface DisabledFacet
 extends WhereValueFacet, DisablingInteractionAdvisor {
 
     public enum Semantics {
-
         /** regular semantics */
         DISABLED,
-
         /** inverted semantics */
         ENABLED;
-
-        public boolean isDisabled() {
-            return this == DISABLED;
-        }
-
-        public boolean isEnabled() {
-            return this == ENABLED;
-        }
+        public boolean isDisabled() { return this == DISABLED; }
+        public boolean isEnabled() { return this == ENABLED; }
     }
 
     /**
      * "Special" phrase returned for facets which are always disabled.
      */
-    String ALWAYS_DISABLED_REASON = "Always disabled";
+    VetoReason ALWAYS_DISABLED_REASON = VetoReason.inferred("Always disabled");
 
     /**
-     * The reason why the (feature of the) target object is currently disabled,
-     * or <tt>null</tt> if enabled.
+     * Optionally the reason why the (feature of the) target object is currently disabled,
+     * or <tt>Optional.empty()</tt> if enabled.
      */
-    @Nullable
-    public String disabledReason(ManagedObject target);
+    public Optional<VetoReason> disabledReason(ManagedObject target);
 
     public Semantics getSemantics();
 
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/members/disabled/DisabledFacetAbstract.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/members/disabled/DisabledFacetAbstract.java
index 1b8e15d58b..af8f28babc 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/members/disabled/DisabledFacetAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/members/disabled/DisabledFacetAbstract.java
@@ -18,12 +18,13 @@
  */
 package org.apache.causeway.core.metamodel.facets.members.disabled;
 
+import java.util.Optional;
 import java.util.function.BiConsumer;
 
 import org.springframework.lang.Nullable;
 
 import org.apache.causeway.applib.annotation.Where;
-import org.apache.causeway.commons.internal.base._Strings;
+import org.apache.causeway.core.metamodel.consent.Consent.VetoReason;
 import org.apache.causeway.core.metamodel.facetapi.Facet;
 import org.apache.causeway.core.metamodel.facetapi.FacetHolder;
 import org.apache.causeway.core.metamodel.facets.WhereValueFacetAbstract;
@@ -48,18 +49,18 @@ implements DisabledFacet {
      * when either sub-classes override {@link #disabledReason(ManagedObject)}
      * or the semantics is inverted (ENABLED)
      */
-    private final @Nullable String reason;
+    private final @Nullable VetoReason reason;
 
     protected DisabledFacetAbstract(
             final Where where,
-            final String reason,
+            final VetoReason reason,
             final FacetHolder holder) {
         this(where, reason, holder, Semantics.DISABLED, Precedence.DEFAULT);
     }
 
     protected DisabledFacetAbstract(
             final Where where,
-            final String reason,
+            final VetoReason reason,
             final FacetHolder holder,
             final Semantics semantics,
             final Precedence precedence) {
@@ -69,22 +70,22 @@ implements DisabledFacet {
     }
 
     @Override
-    public String disabledReason(final ManagedObject targetAdapter) {
+    public Optional<VetoReason> disabledReason(final ManagedObject targetAdapter) {
         if(getSemantics().isEnabled()) {
-            return null;
+            return Optional.empty();
         }
-        return _Strings.isNotEmpty(reason)
+        return Optional.of(reason!=null
                 ? reason
-                : ALWAYS_DISABLED_REASON;
+                : ALWAYS_DISABLED_REASON);
     }
 
     @Override
-    public String disables(final UsabilityContext ic) {
+    public Optional<VetoReason> disables(final UsabilityContext ic) {
         if(getSemantics().isEnabled()) {
             return null;
         }
         final ManagedObject target = ic.getTarget();
-        final String disabledReason = disabledReason(target);
+        final Optional<VetoReason> disabledReason = disabledReason(target);
         return disabledReason;
     }
 
@@ -92,8 +93,8 @@ implements DisabledFacet {
     public final void visitAttributes(final BiConsumer<String, Object> visitor) {
         super.visitAttributes(visitor);
         visitor.accept("semantics", semantics);
-        if(_Strings.isNotEmpty(reason)) {
-            visitor.accept("reason", reason);
+        if(reason!=null) {
+            visitor.accept("reason", reason.string());
         }
     }
 
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/members/disabled/DisabledFacetForContributee.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/members/disabled/DisabledFacetForContributee.java
index 2c73b1025a..96d598bd4d 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/members/disabled/DisabledFacetForContributee.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/members/disabled/DisabledFacetForContributee.java
@@ -19,12 +19,13 @@
 package org.apache.causeway.core.metamodel.facets.members.disabled;
 
 import org.apache.causeway.applib.annotation.Where;
+import org.apache.causeway.core.metamodel.consent.Consent.VetoReason;
 import org.apache.causeway.core.metamodel.facetapi.FacetHolder;
 
 public class DisabledFacetForContributee
 extends DisabledFacetAbstract {
 
-    public DisabledFacetForContributee(final String reason, final FacetHolder holder) {
+    public DisabledFacetForContributee(final VetoReason reason, final FacetHolder holder) {
         super(Where.ANYWHERE, reason, holder);
     }
 }
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/members/disabled/method/DisableForContextFacetNone.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/members/disabled/method/DisableForContextFacetNone.java
index 51e1bad633..e9efc58671 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/members/disabled/method/DisableForContextFacetNone.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/members/disabled/method/DisableForContextFacetNone.java
@@ -18,6 +18,9 @@
  */
 package org.apache.causeway.core.metamodel.facets.members.disabled.method;
 
+import java.util.Optional;
+
+import org.apache.causeway.core.metamodel.consent.Consent.VetoReason;
 import org.apache.causeway.core.metamodel.facetapi.FacetHolder;
 import org.apache.causeway.core.metamodel.interactions.UsabilityContext;
 
@@ -32,11 +35,11 @@ extends DisableForContextFacetAbstract {
      * The reason this object is disabled, or <tt>null</tt> otherwise.
      *
      * <p>
-     * Always returns <tt>null</tt>.
+     * Always returns <tt>Optional.empty()</tt>.
      */
     @Override
-    public String disables(final UsabilityContext ic) {
-        return null;
+    public Optional<VetoReason> disables(final UsabilityContext ic) {
+        return Optional.empty();
     }
 
 }
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/members/disabled/method/DisableForContextFacetViaMethod.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/members/disabled/method/DisableForContextFacetViaMethod.java
index 3aed2fbafe..85df5b5715 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/members/disabled/method/DisableForContextFacetViaMethod.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/members/disabled/method/DisableForContextFacetViaMethod.java
@@ -19,12 +19,14 @@
 package org.apache.causeway.core.metamodel.facets.members.disabled.method;
 
 import java.lang.reflect.Method;
+import java.util.Optional;
 import java.util.function.BiConsumer;
 
 import org.apache.causeway.applib.services.i18n.TranslatableString;
 import org.apache.causeway.applib.services.i18n.TranslationContext;
 import org.apache.causeway.commons.collections.Can;
 import org.apache.causeway.commons.internal.reflection._MethodFacades.MethodFacade;
+import org.apache.causeway.core.metamodel.consent.Consent.VetoReason;
 import org.apache.causeway.core.metamodel.facetapi.FacetHolder;
 import org.apache.causeway.core.metamodel.facets.ImperativeFacet;
 import org.apache.causeway.core.metamodel.interactions.UsabilityContext;
@@ -59,21 +61,21 @@ implements ImperativeFacet {
      * The reason this object is disabled, or <tt>null</tt> otherwise.
      */
     @Override
-    public String disables(final UsabilityContext ic) {
+    public Optional<VetoReason> disables(final UsabilityContext ic) {
         final ManagedObject target = ic.getTarget();
         if (target == null) {
-            return null;
+            return Optional.empty();
         }
         val method = methods.getFirstElseFail().asMethodElseFail(); // expected regular
         final Object returnValue = MmInvokeUtil.invokeAutofit(method, target);
-        if(returnValue instanceof String) {
-            return (String) returnValue;
-        }
-        if(returnValue instanceof TranslatableString) {
-            final TranslatableString ts = (TranslatableString) returnValue;
-            return ts.translate(getTranslationService(), translationContext);
-        }
-        return null;
+        final String reasonString = returnValue instanceof String
+                ? (String) returnValue
+                : returnValue instanceof TranslatableString
+                    ? ((TranslatableString) returnValue).translate(getTranslationService(), translationContext)
+                    : null;
+
+        return Optional.ofNullable(reasonString)
+            .map(VetoReason::explicit);
     }
 
     @Override
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/choices/ChoicesFacetFromBoundedAbstract.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/choices/ChoicesFacetFromBoundedAbstract.java
index 68395f8c7c..e9e2f65732 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/choices/ChoicesFacetFromBoundedAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/choices/ChoicesFacetFromBoundedAbstract.java
@@ -18,8 +18,11 @@
  */
 package org.apache.causeway.core.metamodel.facets.object.choices;
 
+import java.util.Optional;
+
 import org.apache.causeway.applib.query.Query;
 import org.apache.causeway.commons.collections.Can;
+import org.apache.causeway.core.metamodel.consent.Consent.VetoReason;
 import org.apache.causeway.core.metamodel.consent.InteractionInitiatedBy;
 import org.apache.causeway.core.metamodel.facetapi.Facet;
 import org.apache.causeway.core.metamodel.facetapi.FacetAbstract;
@@ -90,7 +93,7 @@ implements
     }
 
     @Override
-    public String disables(final UsabilityContext context) {
+    public Optional<VetoReason> disables(final UsabilityContext context) {
         final ManagedObject target = context.getTarget();
         return disabledReason(target);
     }
@@ -98,8 +101,8 @@ implements
     /**
      * Optional hook method for subclasses to override.
      */
-    public String disabledReason(final ManagedObject inObject) {
-        return "Bounded";
+    public Optional<VetoReason> disabledReason(final ManagedObject inObject) {
+        return VetoReason.inferred("Bounded").toOptional();
     }
 
     @SneakyThrows
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/disabled/DisabledObjectFacetAbstract.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/disabled/DisabledObjectFacetAbstract.java
index 068b4d1c6b..05ea337284 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/disabled/DisabledObjectFacetAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/disabled/DisabledObjectFacetAbstract.java
@@ -18,6 +18,9 @@
  */
 package org.apache.causeway.core.metamodel.facets.object.disabled;
 
+import java.util.Optional;
+
+import org.apache.causeway.core.metamodel.consent.Consent.VetoReason;
 import org.apache.causeway.core.metamodel.facetapi.Facet;
 import org.apache.causeway.core.metamodel.facetapi.FacetAbstract;
 import org.apache.causeway.core.metamodel.facetapi.FacetHolder;
@@ -37,13 +40,13 @@ implements DisabledObjectFacet {
     }
 
     @Override
-    public String disables(final UsabilityContext ic) {
+    public Optional<VetoReason> disables(final UsabilityContext ic) {
         final ManagedObject toDisable = ic.getTarget();
         return toDisable != null
                 ? disabledReason(toDisable)
-                : null;
+                : Optional.empty();
     }
 
-    protected abstract String disabledReason(ManagedObject toDisable);
+    protected abstract Optional<VetoReason> disabledReason(ManagedObject toDisable);
 
 }
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/disabled/method/DisabledObjectFacetViaMethod.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/disabled/method/DisabledObjectFacetViaMethod.java
index 3866982f05..32107de7f3 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/disabled/method/DisabledObjectFacetViaMethod.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/disabled/method/DisabledObjectFacetViaMethod.java
@@ -26,6 +26,7 @@ import org.springframework.lang.Nullable;
 
 import org.apache.causeway.applib.services.i18n.TranslatableString;
 import org.apache.causeway.applib.services.i18n.TranslationContext;
+import org.apache.causeway.core.metamodel.consent.Consent.VetoReason;
 import org.apache.causeway.core.metamodel.facetapi.FacetHolder;
 import org.apache.causeway.core.metamodel.facets.HasImperativeAspect;
 import org.apache.causeway.core.metamodel.facets.ImperativeAspect;
@@ -67,16 +68,17 @@ implements HasImperativeAspect {
     }
 
     @Override
-    public String disabledReason(final ManagedObject domainObject) {
+    public Optional<VetoReason> disabledReason(final ManagedObject domainObject) {
         val returnValue = imperativeAspect.eval(domainObject, null);
-        if(returnValue instanceof String) {
-            return (String)returnValue;
-        }
-        if(returnValue instanceof TranslatableString) {
-            final TranslatableString ts = (TranslatableString)returnValue;
-            return ts.translate(getTranslationService(), translationContext);
-        }
-        return null;
+
+        final String reasonString = returnValue instanceof String
+                ? (String) returnValue
+                : returnValue instanceof TranslatableString
+                    ? ((TranslatableString) returnValue).translate(getTranslationService(), translationContext)
+                    : null;
+
+        return Optional.ofNullable(reasonString)
+            .map(VetoReason::explicit);
     }
 
     @Override
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/domainobject/editing/ImmutableFacetForDomainObjectAnnotation.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/domainobject/editing/ImmutableFacetForDomainObjectAnnotation.java
index f4cda5ee0f..8fdafcbc23 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/domainobject/editing/ImmutableFacetForDomainObjectAnnotation.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/domainobject/editing/ImmutableFacetForDomainObjectAnnotation.java
@@ -24,6 +24,7 @@ import org.apache.causeway.applib.annotation.DomainObject;
 import org.apache.causeway.commons.internal.exceptions._Exceptions;
 import org.apache.causeway.core.config.CausewayConfiguration;
 import org.apache.causeway.core.config.metamodel.facets.DomainObjectConfigOptions;
+import org.apache.causeway.core.metamodel.consent.Consent.VetoReason;
 import org.apache.causeway.core.metamodel.facetapi.FacetHolder;
 import org.apache.causeway.core.metamodel.facets.object.immutable.ImmutableFacet;
 import org.apache.causeway.core.metamodel.facets.object.immutable.ImmutableFacetAbstract;
@@ -46,7 +47,8 @@ extends ImmutableFacetAbstract {
 
         if(domainObjectIfAny.isPresent()) {
             val domainObject = domainObjectIfAny.get();
-            val disabledReason = domainObject.editingDisabledReason();
+            val disabledReason = ImmutableFacetAbstract
+                    .createDisabledReason(domainObject.editingDisabledReason());
 
             switch (domainObject.editing()) {
             case NOT_SPECIFIED:
@@ -77,7 +79,7 @@ extends ImmutableFacetAbstract {
     // -- CONSTRUCTOR
 
     protected ImmutableFacetForDomainObjectAnnotation(
-            final String reason,
+            final VetoReason reason,
             final FacetHolder holder) {
         super(reason, holder);
     }
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/domainobject/editing/ImmutableFacetForDomainObjectAnnotationAsConfigured.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/domainobject/editing/ImmutableFacetForDomainObjectAnnotationAsConfigured.java
index 28af6f588c..d57a8d293b 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/domainobject/editing/ImmutableFacetForDomainObjectAnnotationAsConfigured.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/domainobject/editing/ImmutableFacetForDomainObjectAnnotationAsConfigured.java
@@ -18,6 +18,7 @@
  */
 package org.apache.causeway.core.metamodel.facets.object.domainobject.editing;
 
+import org.apache.causeway.core.metamodel.consent.Consent.VetoReason;
 import org.apache.causeway.core.metamodel.facetapi.FacetHolder;
 import org.apache.causeway.core.metamodel.facets.object.immutable.ImmutableFacet;
 
@@ -25,7 +26,7 @@ public class ImmutableFacetForDomainObjectAnnotationAsConfigured
 extends ImmutableFacetForDomainObjectAnnotation {
 
     public ImmutableFacetForDomainObjectAnnotationAsConfigured(
-            final String reason,
+            final VetoReason reason,
             final FacetHolder holder) {
         super(reason, holder);
     }
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/domainobject/editing/ImmutableFacetFromConfiguration.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/domainobject/editing/ImmutableFacetFromConfiguration.java
index 0fe540e704..f9a0a38231 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/domainobject/editing/ImmutableFacetFromConfiguration.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/domainobject/editing/ImmutableFacetFromConfiguration.java
@@ -18,6 +18,7 @@
  */
 package org.apache.causeway.core.metamodel.facets.object.domainobject.editing;
 
+import org.apache.causeway.core.metamodel.consent.Consent.VetoReason;
 import org.apache.causeway.core.metamodel.facetapi.FacetHolder;
 import org.apache.causeway.core.metamodel.facets.object.immutable.ImmutableFacet;
 import org.apache.causeway.core.metamodel.facets.object.immutable.ImmutableFacetAbstract;
@@ -28,12 +29,13 @@ extends ImmutableFacetAbstract {
     // -- FACTORY
 
     public static ImmutableFacetFromConfiguration create(final FacetHolder holder) {
-        return new ImmutableFacetFromConfiguration("Disabled (by configuration defaults)", holder);
+        return new ImmutableFacetFromConfiguration(
+                VetoReason.explicit("Disabled (by configuration defaults)"), holder);
     }
 
     // -- CONSTRUCTOR
 
-    private ImmutableFacetFromConfiguration(final String reason, final FacetHolder holder) {
+    private ImmutableFacetFromConfiguration(final VetoReason reason, final FacetHolder holder) {
         super(reason, holder, Precedence.LOW);
     }
 
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/immutable/ImmutableFacet.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/immutable/ImmutableFacet.java
index 1a7fb3b27a..1224a1256f 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/immutable/ImmutableFacet.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/immutable/ImmutableFacet.java
@@ -18,6 +18,9 @@
  */
 package org.apache.causeway.core.metamodel.facets.object.immutable;
 
+import java.util.Optional;
+
+import org.apache.causeway.core.metamodel.consent.Consent.VetoReason;
 import org.apache.causeway.core.metamodel.facetapi.Facet;
 import org.apache.causeway.core.metamodel.facetapi.FacetHolder;
 import org.apache.causeway.core.metamodel.facets.object.value.ValueFacet;
@@ -44,6 +47,6 @@ extends Facet, DisablingInteractionAdvisor {
      */
     ImmutableFacet clone(FacetHolder holder);
 
-    String disabledReason(ManagedObject targetAdapter);
+    Optional<VetoReason> disabledReason(ManagedObject targetAdapter);
 
 }
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/immutable/ImmutableFacetAbstract.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/immutable/ImmutableFacetAbstract.java
index 9165d43709..ac324d8877 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/immutable/ImmutableFacetAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/immutable/ImmutableFacetAbstract.java
@@ -18,17 +18,21 @@
  */
 package org.apache.causeway.core.metamodel.facets.object.immutable;
 
+import java.util.Optional;
 import java.util.function.BiConsumer;
 
 import org.springframework.lang.Nullable;
 
 import org.apache.causeway.commons.internal.base._Strings;
+import org.apache.causeway.core.metamodel.consent.Consent.VetoReason;
 import org.apache.causeway.core.metamodel.facetapi.Facet;
 import org.apache.causeway.core.metamodel.facetapi.FacetAbstract;
 import org.apache.causeway.core.metamodel.facetapi.FacetHolder;
 import org.apache.causeway.core.metamodel.interactions.UsabilityContext;
 import org.apache.causeway.core.metamodel.object.ManagedObject;
 
+import lombok.NonNull;
+
 public abstract class ImmutableFacetAbstract
 extends FacetAbstract
 implements ImmutableFacet {
@@ -37,17 +41,16 @@ implements ImmutableFacet {
         return ImmutableFacet.class;
     }
 
-    protected final @Nullable String reason;
+    protected final @NonNull VetoReason reason;
 
-    public ImmutableFacetAbstract(
-            final String reason,
+    protected ImmutableFacetAbstract(
+            final VetoReason reason,
             final FacetHolder holder) {
-        super(type(), holder);
-        this.reason = reason;
+        this(reason, holder, Facet.Precedence.DEFAULT);
     }
 
-    public ImmutableFacetAbstract(
-            final String reason,
+    protected ImmutableFacetAbstract(
+            final VetoReason reason,
             final FacetHolder holder,
             final Facet.Precedence precedence) {
         super(type(), holder, precedence);
@@ -55,23 +58,21 @@ implements ImmutableFacet {
     }
 
     @Override
-    public final String disabledReason(final ManagedObject targetAdapter) {
-        return !_Strings.isNullOrEmpty(reason)
-                ? reason
-                : "Always immmutable"; // assuming there is no ImmutableFacet(s) with inverted semantics
+    public final Optional<VetoReason> disabledReason(final ManagedObject targetAdapter) {
+        return Optional.of(reason);
     }
 
     @Override
     public final void visitAttributes(final BiConsumer<String, Object> visitor) {
         super.visitAttributes(visitor);
-        visitor.accept("reason", reason);
+        visitor.accept("reason", reason.string());
     }
 
     /**
      * Immutable facet only prevents changes to a property or a collection.
      */
     @Override
-    public String disables(final UsabilityContext ic) {
+    public Optional<VetoReason> disables(final UsabilityContext ic) {
         final ManagedObject target = ic.getTarget();
         switch (ic.getInteractionType()) {
         case PROPERTY_MODIFY:
@@ -79,9 +80,18 @@ implements ImmutableFacet {
         case COLLECTION_REMOVE_FROM:
             return disabledReason(target);
         default:
-            return null;
+            return Optional.empty();
         }
     }
 
+    // -- HELPER
+
+    protected static VetoReason createDisabledReason(@Nullable final String explicitReasonString) {
+        return _Strings.nonEmpty(explicitReasonString)
+                .map(VetoReason::explicit)
+                // assuming there is no ImmutableFacet(s) with inverted semantics
+                .orElseGet(()->VetoReason.inferred("Always immmutable"));
+    }
+
 
 }
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/value/ImmutableFacetViaValueSemantics.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/value/ImmutableFacetViaValueSemantics.java
index 04bfd0da8c..6cfea184fb 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/value/ImmutableFacetViaValueSemantics.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/value/ImmutableFacetViaValueSemantics.java
@@ -18,6 +18,7 @@
  */
 package org.apache.causeway.core.metamodel.facets.object.value;
 
+import org.apache.causeway.core.metamodel.consent.Consent.VetoReason;
 import org.apache.causeway.core.metamodel.facetapi.FacetHolder;
 import org.apache.causeway.core.metamodel.facets.object.immutable.ImmutableFacet;
 import org.apache.causeway.core.metamodel.facets.object.immutable.ImmutableFacetAbstract;
@@ -26,7 +27,7 @@ public class ImmutableFacetViaValueSemantics
 extends ImmutableFacetAbstract {
 
     public ImmutableFacetViaValueSemantics(final FacetHolder holder) {
-        super("Value types are immutable", holder);
+        super(VetoReason.inferred("Value types are immutable"), holder);
     }
 
     @Override
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/param/disable/ActionParameterDisabledFacet.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/param/disable/ActionParameterDisabledFacet.java
index adbf9f7892..9e17a7b44e 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/param/disable/ActionParameterDisabledFacet.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/param/disable/ActionParameterDisabledFacet.java
@@ -18,7 +18,10 @@
  */
 package org.apache.causeway.core.metamodel.facets.param.disable;
 
+import java.util.Optional;
+
 import org.apache.causeway.commons.collections.Can;
+import org.apache.causeway.core.metamodel.consent.Consent.VetoReason;
 import org.apache.causeway.core.metamodel.facetapi.Facet;
 import org.apache.causeway.core.metamodel.interactions.DisablingInteractionAdvisor;
 import org.apache.causeway.core.metamodel.object.ManagedObject;
@@ -33,8 +36,8 @@ import org.apache.causeway.core.metamodel.object.ManagedObject;
 public interface ActionParameterDisabledFacet extends Facet, DisablingInteractionAdvisor {
 
     /**
-     * Reason why the parameter is disabled, or <tt>null</tt> if okay.
+     * Reason why the parameter is disabled, or <tt>Optional.empts()</tt> if okay.
      */
-    public String disabledReason(ManagedObject target, Can<ManagedObject> arguments);
+    public Optional<VetoReason> disabledReason(ManagedObject target, Can<ManagedObject> arguments);
 
 }
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/param/disable/ActionParameterDisabledFacetAbstract.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/param/disable/ActionParameterDisabledFacetAbstract.java
index b435f7858f..f679889128 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/param/disable/ActionParameterDisabledFacetAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/param/disable/ActionParameterDisabledFacetAbstract.java
@@ -18,6 +18,9 @@
  */
 package org.apache.causeway.core.metamodel.facets.param.disable;
 
+import java.util.Optional;
+
+import org.apache.causeway.core.metamodel.consent.Consent.VetoReason;
 import org.apache.causeway.core.metamodel.facetapi.Facet;
 import org.apache.causeway.core.metamodel.facetapi.FacetAbstract;
 import org.apache.causeway.core.metamodel.facetapi.FacetHolder;
@@ -37,9 +40,9 @@ implements ActionParameterDisabledFacet {
     }
 
     @Override
-    public String disables(final UsabilityContext context) {
+    public Optional<VetoReason> disables(final UsabilityContext context) {
         if (!(context instanceof ActionArgUsabilityContext)) {
-            return null;
+            return Optional.empty();
         }
         final ActionArgUsabilityContext actionArgUsabilityContext = (ActionArgUsabilityContext) context;
         return disabledReason(actionArgUsabilityContext.getTarget(), actionArgUsabilityContext.getArgs());
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/param/disable/method/ActionParameterDisabledFacetViaMethod.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/param/disable/method/ActionParameterDisabledFacetViaMethod.java
index ad5ac576b1..12c46f967f 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/param/disable/method/ActionParameterDisabledFacetViaMethod.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/param/disable/method/ActionParameterDisabledFacetViaMethod.java
@@ -27,6 +27,7 @@ import org.apache.causeway.applib.services.i18n.TranslatableString;
 import org.apache.causeway.applib.services.i18n.TranslationContext;
 import org.apache.causeway.commons.collections.Can;
 import org.apache.causeway.commons.internal.reflection._MethodFacades.MethodFacade;
+import org.apache.causeway.core.metamodel.consent.Consent.VetoReason;
 import org.apache.causeway.core.metamodel.facetapi.FacetHolder;
 import org.apache.causeway.core.metamodel.facets.ImperativeFacet;
 import org.apache.causeway.core.metamodel.facets.param.disable.ActionParameterDisabledFacetAbstract;
@@ -62,20 +63,20 @@ implements ImperativeFacet {
     }
 
     @Override
-    public String disabledReason(
+    public Optional<VetoReason> disabledReason(
             final ManagedObject owningAdapter,
             final Can<ManagedObject> pendingArgs) {
 
         val method = methods.getFirstElseFail();
         final Object returnValue = MmInvokeUtil.invokeAutofit(patConstructor, method, owningAdapter, pendingArgs);
-        if(returnValue instanceof String) {
-            return (String) returnValue;
-        }
-        if(returnValue instanceof TranslatableString) {
-            final TranslatableString ts = (TranslatableString) returnValue;
-            return ts.translate(getTranslationService(), translationContext);
-        }
-        return null;
+        final String reasonString = returnValue instanceof String
+                ? (String) returnValue
+                : returnValue instanceof TranslatableString
+                    ? ((TranslatableString) returnValue).translate(getTranslationService(), translationContext)
+                    : null;
+
+        return Optional.ofNullable(reasonString)
+            .map(VetoReason::explicit);
     }
 
     @Override
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/properties/disabled/fromimmutable/DisabledFacetOnPropertyFromImmutable.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/properties/disabled/fromimmutable/DisabledFacetOnPropertyFromImmutable.java
index ef7ca20939..6cf2b1c3b1 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/properties/disabled/fromimmutable/DisabledFacetOnPropertyFromImmutable.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/properties/disabled/fromimmutable/DisabledFacetOnPropertyFromImmutable.java
@@ -18,8 +18,10 @@
  */
 package org.apache.causeway.core.metamodel.facets.properties.disabled.fromimmutable;
 
+import java.util.Optional;
+
 import org.apache.causeway.applib.annotation.Where;
-import org.apache.causeway.commons.internal.base._Strings;
+import org.apache.causeway.core.metamodel.consent.Consent.VetoReason;
 import org.apache.causeway.core.metamodel.facetapi.FacetHolder;
 import org.apache.causeway.core.metamodel.facets.FacetedMethod;
 import org.apache.causeway.core.metamodel.facets.members.disabled.DisabledFacetAbstract;
@@ -51,21 +53,20 @@ extends DisabledFacetAbstract {
             final @NonNull FacetHolder holder,
             final @NonNull ImmutableFacet reasonProvidingImmutableFacet) {
 
-        super(Where.ANYWHERE,
+        super(Where.ANYWHERE, VetoReason.inferred(
                 "calculated at runtime, delegating to ImmutableFacet " +
-                        reasonProvidingImmutableFacet.getClass(),
+                        reasonProvidingImmutableFacet.getClass()),
                 holder);
 
         this.reasonProvidingImmutableFacet = reasonProvidingImmutableFacet;
     }
 
     @Override
-    public String disabledReason(final ManagedObject target) {
+    public Optional<VetoReason> disabledReason(final ManagedObject target) {
         val reason = reasonProvidingImmutableFacet.disabledReason(target);
         // ensure non empty reason
-        return _Strings.isNotEmpty(reason)
-                ? reason
-                : "Immutable";
+        return reason
+                .or(()->VetoReason.inferred("Immutable").toOptional());
     }
 
 
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/properties/property/disabled/DisabledFacetForPropertyAnnotation.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/properties/property/disabled/DisabledFacetForPropertyAnnotation.java
index 664e2a2082..d7cf16c5d4 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/properties/property/disabled/DisabledFacetForPropertyAnnotation.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/properties/property/disabled/DisabledFacetForPropertyAnnotation.java
@@ -23,6 +23,8 @@ import java.util.Optional;
 import org.apache.causeway.applib.annotation.Editing;
 import org.apache.causeway.applib.annotation.Property;
 import org.apache.causeway.applib.annotation.Where;
+import org.apache.causeway.commons.internal.base._Strings;
+import org.apache.causeway.core.metamodel.consent.Consent.VetoReason;
 import org.apache.causeway.core.metamodel.facetapi.FacetHolder;
 import org.apache.causeway.core.metamodel.facets.members.disabled.DisabledFacet;
 import org.apache.causeway.core.metamodel.facets.members.disabled.DisabledFacetAbstract;
@@ -47,7 +49,8 @@ extends DisabledFacetAbstract {
                 return null;
 
             case DISABLED:
-                final String disabledReason = property.editingDisabledReason();
+                final String reasonString = _Strings.nullToEmpty(property.editingDisabledReason());
+                final VetoReason disabledReason = VetoReason.explicit(reasonString);
                 return new DisabledFacetForPropertyAnnotation(disabledReason, holder);
             case ENABLED:
                 return new DisabledFacetForPropertyAnnotationInvertedSemantics(holder);
@@ -57,7 +60,7 @@ extends DisabledFacetAbstract {
         });
     }
 
-    private DisabledFacetForPropertyAnnotation(final String reason, final FacetHolder holder) {
+    private DisabledFacetForPropertyAnnotation(final VetoReason reason, final FacetHolder holder) {
         super(Where.EVERYWHERE, reason, holder);
     }
 
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/properties/property/disabled/DisabledFacetForPropertyAnnotationInvertedSemantics.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/properties/property/disabled/DisabledFacetForPropertyAnnotationInvertedSemantics.java
index 9f3253f167..113c74c43a 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/properties/property/disabled/DisabledFacetForPropertyAnnotationInvertedSemantics.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/properties/property/disabled/DisabledFacetForPropertyAnnotationInvertedSemantics.java
@@ -19,6 +19,7 @@
 package org.apache.causeway.core.metamodel.facets.properties.property.disabled;
 
 import org.apache.causeway.applib.annotation.Where;
+import org.apache.causeway.core.metamodel.consent.Consent.VetoReason;
 import org.apache.causeway.core.metamodel.facetapi.FacetHolder;
 import org.apache.causeway.core.metamodel.facets.members.disabled.DisabledFacetAbstract;
 
@@ -26,8 +27,8 @@ public class DisabledFacetForPropertyAnnotationInvertedSemantics
 extends DisabledFacetAbstract {
 
     DisabledFacetForPropertyAnnotationInvertedSemantics(final FacetHolder holder) {
-        super(Where.EVERYWHERE,
-                "enabled, based on Property annotation with inverted semantics",
+        super(Where.EVERYWHERE, VetoReason.inferred(
+                "enabled, based on Property annotation with inverted semantics"),
                 holder,
                 Semantics.ENABLED, Precedence.DEFAULT);
     }
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/properties/property/modify/PropertyDomainEventFacetAbstract.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/properties/property/modify/PropertyDomainEventFacetAbstract.java
index e48156e112..614a515af7 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/properties/property/modify/PropertyDomainEventFacetAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/properties/property/modify/PropertyDomainEventFacetAbstract.java
@@ -18,6 +18,7 @@
  */
 package org.apache.causeway.core.metamodel.facets.properties.property.modify;
 
+import java.util.Optional;
 import java.util.function.BiConsumer;
 
 import org.apache.causeway.applib.annotation.DomainObject;
@@ -27,6 +28,7 @@ import org.apache.causeway.applib.services.i18n.TranslatableString;
 import org.apache.causeway.applib.services.i18n.TranslationContext;
 import org.apache.causeway.applib.services.i18n.TranslationService;
 import org.apache.causeway.commons.internal.base._Casts;
+import org.apache.causeway.core.metamodel.consent.Consent.VetoReason;
 import org.apache.causeway.core.metamodel.facetapi.FacetHolder;
 import org.apache.causeway.core.metamodel.facets.DomainEventHelper;
 import org.apache.causeway.core.metamodel.facets.SingleClassValueFacetAbstract;
@@ -100,7 +102,7 @@ extends SingleClassValueFacetAbstract implements PropertyDomainEventFacet {
     }
 
     @Override
-    public String disables(final UsabilityContext ic) {
+    public Optional<VetoReason> disables(final UsabilityContext ic) {
 
         final PropertyDomainEvent<?, ?> event =
                 domainEventHelper.postEventForProperty(
@@ -108,14 +110,18 @@ extends SingleClassValueFacetAbstract implements PropertyDomainEventFacet {
                         getEventType(), null,
                         getFacetHolder(), ic.getHead(),
                         null, null);
-        if (event != null && event.isDisabled()) {
+        if (event != null
+                && event.isDisabled()) {
+
             final TranslatableString reasonTranslatable = event.getDisabledReasonTranslatable();
-            if(reasonTranslatable != null) {
-                return reasonTranslatable.translate(translationService, translationContext);
-            }
-            return event.getDisabledReason();
+            final String reasonString = reasonTranslatable != null
+                    ? reasonTranslatable.translate(translationService, translationContext)
+                    : event.getDisabledReason();
+
+            return Optional.ofNullable(reasonString)
+                .map(VetoReason::explicit);
         }
-        return null;
+        return Optional.empty();
     }
 
     @Override
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/DisablingInteractionAdvisor.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/DisablingInteractionAdvisor.java
index 8db22551be..a842026272 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/DisablingInteractionAdvisor.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/DisablingInteractionAdvisor.java
@@ -18,6 +18,9 @@
  */
 package org.apache.causeway.core.metamodel.interactions;
 
+import java.util.Optional;
+
+import org.apache.causeway.core.metamodel.consent.Consent.VetoReason;
 import org.apache.causeway.core.metamodel.facetapi.FacetAbstract.DisablingOrEnabling;
 
 /**
@@ -40,6 +43,6 @@ extends InteractionAdvisorFacet, DisablingOrEnabling {
      * They must however guard against a <tt>null</tt>
      * {@link InteractionContext#getTarget() target} - this is not guaranteed to be populated.
      */
-    String disables(UsabilityContext ic);
+    Optional<VetoReason> disables(UsabilityContext ic);
 
 }
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/InteractionUtils.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/InteractionUtils.java
index b494dc113c..2f3aa0dfce 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/InteractionUtils.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/InteractionUtils.java
@@ -18,6 +18,7 @@
  */
 package org.apache.causeway.core.metamodel.interactions;
 
+import org.apache.causeway.core.metamodel.consent.Consent.VetoReason;
 import org.apache.causeway.core.metamodel.consent.InteractionAdvisor;
 import org.apache.causeway.core.metamodel.consent.InteractionResult;
 import org.apache.causeway.core.metamodel.consent.InteractionResultSet;
@@ -30,7 +31,7 @@ import lombok.experimental.UtilityClass;
 @UtilityClass
 public final class InteractionUtils {
 
-    public static InteractionResult isVisibleResult(FacetHolder facetHolder, VisibilityContext context) {
+    public static InteractionResult isVisibleResult(final FacetHolder facetHolder, final VisibilityContext context) {
 
         val iaResult = new InteractionResult(context.createInteractionEvent());
 
@@ -45,21 +46,21 @@ public final class InteractionUtils {
     }
 
 
-    public static InteractionResult isUsableResult(FacetHolder facetHolder, UsabilityContext context) {
+    public static InteractionResult isUsableResult(final FacetHolder facetHolder, final UsabilityContext context) {
 
         val isResult = new InteractionResult(context.createInteractionEvent());
 
         facetHolder.streamFacets(DisablingInteractionAdvisor.class)
         .filter(advisor->compatible(advisor, context))
         .forEach(advisor->{
-            val disablingReason = advisor.disables(context);
+            val disablingReason = advisor.disables(context).map(VetoReason::string).orElse(null);
             isResult.advise(disablingReason, advisor);
         });
 
         return isResult;
     }
 
-    public static InteractionResult isValidResult(FacetHolder facetHolder, ValidityContext context) {
+    public static InteractionResult isValidResult(final FacetHolder facetHolder, final ValidityContext context) {
 
         val iaResult = new InteractionResult(context.createInteractionEvent());
 
@@ -74,14 +75,14 @@ public final class InteractionUtils {
     }
 
     public static InteractionResultSet isValidResultSet(
-            FacetHolder facetHolder,
-            ValidityContext context,
-            InteractionResultSet resultSet) {
+            final FacetHolder facetHolder,
+            final ValidityContext context,
+            final InteractionResultSet resultSet) {
 
         return resultSet.add(isValidResult(facetHolder, context));
     }
 
-    private static boolean compatible(InteractionAdvisor advisor, InteractionContext ic) {
+    private static boolean compatible(final InteractionAdvisor advisor, final InteractionContext ic) {
 
         if(advisor instanceof ActionDomainEventFacet) {
             return ic instanceof ActionInteractionContext;
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/postprocessors/allbutparam/authorization/AuthorizationFacetAbstract.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/postprocessors/allbutparam/authorization/AuthorizationFacetAbstract.java
index ea3f4dd3b9..6e4ea31f9f 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/postprocessors/allbutparam/authorization/AuthorizationFacetAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/postprocessors/allbutparam/authorization/AuthorizationFacetAbstract.java
@@ -18,6 +18,9 @@
  */
 package org.apache.causeway.core.metamodel.postprocessors.allbutparam.authorization;
 
+import java.util.Optional;
+
+import org.apache.causeway.core.metamodel.consent.Consent.VetoReason;
 import org.apache.causeway.core.metamodel.facetapi.Facet;
 import org.apache.causeway.core.metamodel.facetapi.FacetAbstract;
 import org.apache.causeway.core.metamodel.facetapi.FacetHolder;
@@ -67,10 +70,10 @@ implements AuthorizationFacet {
     }
 
     @Override
-    public String disables(final UsabilityContext ic) {
+    public Optional<VetoReason> disables(final UsabilityContext ic) {
 
         if(ic.getHead().getOwner().getSpecification().isValue()) {
-            return null; // never disable value-types
+            return Optional.empty(); // never disable value-types
         }
 
         val disables = authorizationManager
@@ -84,7 +87,7 @@ implements AuthorizationFacet {
             log.debug("disables[{}] -> {}", ic.getIdentifier(), disables);
         }
 
-        return disables;
+        return Optional.ofNullable(disables).map(VetoReason::explicit);
     }
 
 
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/specloader/specimpl/OneToManyAssociationMixedIn.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/specloader/specimpl/OneToManyAssociationMixedIn.java
index e780f14a9c..30a8f10463 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/specloader/specimpl/OneToManyAssociationMixedIn.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/specloader/specimpl/OneToManyAssociationMixedIn.java
@@ -23,6 +23,7 @@ import org.apache.causeway.applib.annotation.Domain;
 import org.apache.causeway.applib.annotation.DomainObject;
 import org.apache.causeway.applib.id.LogicalType;
 import org.apache.causeway.commons.collections.Can;
+import org.apache.causeway.core.metamodel.consent.Consent.VetoReason;
 import org.apache.causeway.core.metamodel.consent.InteractionInitiatedBy;
 import org.apache.causeway.core.metamodel.facetapi.FacetHolder;
 import org.apache.causeway.core.metamodel.facetapi.FacetUtil;
@@ -137,7 +138,7 @@ implements MixedInMember {
             return originalFacet;
         }
         // ensure that the contributed association is always disabled
-        return new DisabledFacetForContributee("Contributed collection", this);
+        return new DisabledFacetForContributee(VetoReason.inferred("Contributed collection"), this);
     }
 
     @Override
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/specloader/specimpl/OneToOneAssociationMixedIn.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/specloader/specimpl/OneToOneAssociationMixedIn.java
index 3be509b437..f2cbe62944 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/specloader/specimpl/OneToOneAssociationMixedIn.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/specloader/specimpl/OneToOneAssociationMixedIn.java
@@ -22,6 +22,7 @@ import org.apache.causeway.applib.Identifier;
 import org.apache.causeway.applib.annotation.Domain;
 import org.apache.causeway.applib.id.LogicalType;
 import org.apache.causeway.commons.collections.Can;
+import org.apache.causeway.core.metamodel.consent.Consent.VetoReason;
 import org.apache.causeway.core.metamodel.consent.InteractionInitiatedBy;
 import org.apache.causeway.core.metamodel.facetapi.FacetHolder;
 import org.apache.causeway.core.metamodel.facetapi.FacetUtil;
@@ -118,7 +119,7 @@ implements MixedInMember {
             return originalFacet;
         }
         // ensure that the contributed association is always disabled
-        return new DisabledFacetForContributee("Contributed property", this);
+        return new DisabledFacetForContributee(VetoReason.inferred("Contributed property"), this);
     }
 
     @Override
diff --git a/core/metamodel/src/test/java/org/apache/causeway/core/metamodel/facets/properties/property/PropertyAnnotationFacetFactoryTest.java b/core/metamodel/src/test/java/org/apache/causeway/core/metamodel/facets/properties/property/PropertyAnnotationFacetFactoryTest.java
index 3b6bd43407..76eb90f51c 100644
--- a/core/metamodel/src/test/java/org/apache/causeway/core/metamodel/facets/properties/property/PropertyAnnotationFacetFactoryTest.java
+++ b/core/metamodel/src/test/java/org/apache/causeway/core/metamodel/facets/properties/property/PropertyAnnotationFacetFactoryTest.java
@@ -44,6 +44,7 @@ import org.apache.causeway.applib.annotation.Where;
 import org.apache.causeway.applib.events.domain.PropertyDomainEvent;
 import org.apache.causeway.applib.spec.Specification;
 import org.apache.causeway.core.metamodel.commons.matchers.CausewayMatchers;
+import org.apache.causeway.core.metamodel.consent.Consent.VetoReason;
 import org.apache.causeway.core.metamodel.consent.InteractionInitiatedBy;
 import org.apache.causeway.core.metamodel.facetapi.Facet;
 import org.apache.causeway.core.metamodel.facetapi.FacetHolder;
@@ -538,7 +539,7 @@ class PropertyAnnotationFacetFactoryTest extends AbstractFacetFactoryJupiterTest
             assertTrue(disabledFacet instanceof DisabledFacetForPropertyAnnotation);
             val disabledFacet2 = (DisabledFacetForPropertyAnnotation) disabledFacet;
             assertThat(disabledFacet.where(), is(Where.EVERYWHERE));
-            assertThat(disabledFacet2.disabledReason(null), is(expectedDisabledReason));
+            assertThat(disabledFacet2.disabledReason(null).map(VetoReason::string).orElse(null), is(expectedDisabledReason));
         }
 
     }
diff --git a/extensions/security/secman/integration/src/main/java/org/apache/causeway/extensions/secman/integration/facets/TenantedAuthorizationFacetDefault.java b/extensions/security/secman/integration/src/main/java/org/apache/causeway/extensions/secman/integration/facets/TenantedAuthorizationFacetDefault.java
index fd3ebf15f6..784fee3b05 100644
--- a/extensions/security/secman/integration/src/main/java/org/apache/causeway/extensions/secman/integration/facets/TenantedAuthorizationFacetDefault.java
+++ b/extensions/security/secman/integration/src/main/java/org/apache/causeway/extensions/secman/integration/facets/TenantedAuthorizationFacetDefault.java
@@ -18,14 +18,14 @@
  */
 package org.apache.causeway.extensions.secman.integration.facets;
 
-import lombok.val;
-
 import java.util.List;
+import java.util.Optional;
 
 import javax.inject.Provider;
 
 import org.apache.causeway.applib.services.queryresultscache.QueryResultsCache;
 import org.apache.causeway.applib.services.user.UserService;
+import org.apache.causeway.core.metamodel.consent.Consent.VetoReason;
 import org.apache.causeway.core.metamodel.facetapi.Facet;
 import org.apache.causeway.core.metamodel.facetapi.FacetAbstract;
 import org.apache.causeway.core.metamodel.facetapi.FacetHolder;
@@ -35,7 +35,8 @@ import org.apache.causeway.core.metamodel.interactions.VisibilityContext;
 import org.apache.causeway.extensions.secman.applib.tenancy.spi.ApplicationTenancyEvaluator;
 import org.apache.causeway.extensions.secman.applib.user.dom.ApplicationUser;
 import org.apache.causeway.extensions.secman.applib.user.dom.ApplicationUserRepository;
-import org.springframework.lang.Nullable;
+
+import lombok.val;
 
 public class TenantedAuthorizationFacetDefault
 extends FacetAbstract
@@ -65,20 +66,21 @@ implements TenantedAuthorizationFacet {
 
     @Override
     public String hides(final VisibilityContext ic) {
-        return evaluate(ApplicationTenancyEvaluator::hides, ic.getHead());
+        return evaluate(ApplicationTenancyEvaluator::hides, ic.getHead())
+                .orElse(null);
     }
 
     @Override
-    public String disables(final UsabilityContext ic) {
-        return evaluate(ApplicationTenancyEvaluator::disables, ic.getHead());
+    public Optional<VetoReason> disables(final UsabilityContext ic) {
+        return evaluate(ApplicationTenancyEvaluator::disables, ic.getHead())
+                .map(VetoReason::explicit);
     }
 
-    @Nullable
-    private String evaluate(EvaluationDispatcher evaluationDispatcher, InteractionHead head) {
+    private Optional<String> evaluate(final EvaluationDispatcher evaluationDispatcher, final InteractionHead head) {
         if(evaluators == null
                 || evaluators.isEmpty()
                 || userService.isCurrentUserWithSudoAccessAllRole()) {
-            return null;
+            return Optional.empty();
         }
 
         val domainObject = head.getOwner().getPojo();
@@ -87,16 +89,16 @@ implements TenantedAuthorizationFacet {
         val applicationUser = findApplicationUser(userName);
         if (applicationUser == null) {
             // not expected, but best to be safe...
-            return "Could not locate application user for " + userName;
+            return Optional.of("Could not locate application user for " + userName);
         }
 
         for (val evaluator : evaluators) {
-            final String reason = evaluationDispatcher.dispatch(evaluator, domainObject, applicationUser);
-            if(reason != null) {
-                return reason;
+            final String reasonString = evaluationDispatcher.dispatch(evaluator, domainObject, applicationUser);
+            if(reasonString != null) {
+                return Optional.of(reasonString);
             }
         }
-        return null;
+        return Optional.empty();
     }
 
     interface EvaluationDispatcher {
diff --git a/persistence/jdo/metamodel/src/main/java/org/apache/causeway/persistence/jdo/metamodel/facets/prop/primarykey/DisabledFacetFromJdoPrimaryKeyAnnotation.java b/persistence/jdo/metamodel/src/main/java/org/apache/causeway/persistence/jdo/metamodel/facets/prop/primarykey/DisabledFacetFromJdoPrimaryKeyAnnotation.java
index 8202ba63de..896f4db796 100644
--- a/persistence/jdo/metamodel/src/main/java/org/apache/causeway/persistence/jdo/metamodel/facets/prop/primarykey/DisabledFacetFromJdoPrimaryKeyAnnotation.java
+++ b/persistence/jdo/metamodel/src/main/java/org/apache/causeway/persistence/jdo/metamodel/facets/prop/primarykey/DisabledFacetFromJdoPrimaryKeyAnnotation.java
@@ -19,6 +19,7 @@
 package org.apache.causeway.persistence.jdo.metamodel.facets.prop.primarykey;
 
 import org.apache.causeway.applib.annotation.Where;
+import org.apache.causeway.core.metamodel.consent.Consent.VetoReason;
 import org.apache.causeway.core.metamodel.facetapi.FacetHolder;
 import org.apache.causeway.core.metamodel.facets.members.disabled.DisabledFacetAbstract;
 
@@ -30,7 +31,7 @@ public class DisabledFacetFromJdoPrimaryKeyAnnotation
 extends DisabledFacetAbstract {
 
     public DisabledFacetFromJdoPrimaryKeyAnnotation(final FacetHolder holder) {
-        super(Where.ANYWHERE, "Primary-keys are immutable", holder);
+        super(Where.ANYWHERE, VetoReason.inferred("Primary-keys are immutable"), holder);
     }
 
 }