You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@causeway.apache.org by da...@apache.org on 2026/10/09 14:49:15 UTC

(causeway) branch CAUSEWAY-4058 created (now c04587a7bcc)

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

danhaywood pushed a change to branch CAUSEWAY-4058
in repository https://gitbox.apache.org/repos/asf/causeway.git


      at c04587a7bcc CAUSEWAY-4058: archive current action invocation context

This branch includes the following new commits:

     new 5c43e44b085 CAUSEWAY-4058: propose current action invocation context
     new be228adf4ac CAUSEWAY-4058: expose current action invocation context
     new c04587a7bcc CAUSEWAY-4058: archive current action invocation context

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(causeway) 03/03: CAUSEWAY-4058: archive current action invocation context

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

danhaywood pushed a commit to branch CAUSEWAY-4058
in repository https://gitbox.apache.org/repos/asf/causeway.git

commit c04587a7bcc03117130a3b78b8dbe90ef9657cc3
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Fri Oct 9 15:49:09 2026 +0100

    CAUSEWAY-4058: archive current action invocation context
---
 .../2026-10-09-port-current-action-context}/.openspec.yaml        | 0
 .../2026-10-09-port-current-action-context}/design.md             | 0
 .../2026-10-09-port-current-action-context}/proposal.md           | 0
 .../specs/current-action-invocation-context/spec.md               | 0
 .../2026-10-09-port-current-action-context}/tasks.md              | 0
 .../2026-10-09-port-current-action-context}/validation.md         | 4 ++++
 openspec/planned-changes/otel-forward-port-roadmap.md             | 8 +++++---
 .../specs/current-action-invocation-context/spec.md               | 6 +++++-
 8 files changed, 14 insertions(+), 4 deletions(-)

diff --git a/openspec/changes/port-current-action-context/.openspec.yaml b/openspec/changes/archive/2026-10-09-port-current-action-context/.openspec.yaml
similarity index 100%
rename from openspec/changes/port-current-action-context/.openspec.yaml
rename to openspec/changes/archive/2026-10-09-port-current-action-context/.openspec.yaml
diff --git a/openspec/changes/port-current-action-context/design.md b/openspec/changes/archive/2026-10-09-port-current-action-context/design.md
similarity index 100%
rename from openspec/changes/port-current-action-context/design.md
rename to openspec/changes/archive/2026-10-09-port-current-action-context/design.md
diff --git a/openspec/changes/port-current-action-context/proposal.md b/openspec/changes/archive/2026-10-09-port-current-action-context/proposal.md
similarity index 100%
rename from openspec/changes/port-current-action-context/proposal.md
rename to openspec/changes/archive/2026-10-09-port-current-action-context/proposal.md
diff --git a/openspec/changes/port-current-action-context/specs/current-action-invocation-context/spec.md b/openspec/changes/archive/2026-10-09-port-current-action-context/specs/current-action-invocation-context/spec.md
similarity index 100%
copy from openspec/changes/port-current-action-context/specs/current-action-invocation-context/spec.md
copy to openspec/changes/archive/2026-10-09-port-current-action-context/specs/current-action-invocation-context/spec.md
diff --git a/openspec/changes/port-current-action-context/tasks.md b/openspec/changes/archive/2026-10-09-port-current-action-context/tasks.md
similarity index 100%
rename from openspec/changes/port-current-action-context/tasks.md
rename to openspec/changes/archive/2026-10-09-port-current-action-context/tasks.md
diff --git a/openspec/changes/port-current-action-context/validation.md b/openspec/changes/archive/2026-10-09-port-current-action-context/validation.md
similarity index 87%
rename from openspec/changes/port-current-action-context/validation.md
rename to openspec/changes/archive/2026-10-09-port-current-action-context/validation.md
index 5a618480852..74fe6aa4dec 100644
--- a/openspec/changes/port-current-action-context/validation.md
+++ b/openspec/changes/archive/2026-10-09-port-current-action-context/validation.md
@@ -36,3 +36,7 @@ Rendered all three changed AsciiDoc pages with the locally installed Asciidoctor
 ## Limits
 
 This additive invocation API emits no new spans and requires no observation profile. No interactive browser or external exporter verification applies. Direct Java calls do not introduce framework execution frames and can still see an enclosing invocation. RuleChecking is aggregate mode information, not individual rule provenance. This implementation is based on the local preceding feature branch; remote main was not fetched or merged during apply.
+
+## Rebased archive verification (2026-10-09)
+
+After the user rebased onto main merge `ce0c8352dee`, planning and implementation checkpoints are `5c43e44b085` and `be228adf4ac`. Fresh focused reactor verification passed 29 tests; the full interact module again reported 79 tests, 77 executed and 2 existing disabled tests, with no failures/errors. All three documentation pages rendered and strict change validation passed. Specs were synced during archive.
diff --git a/openspec/planned-changes/otel-forward-port-roadmap.md b/openspec/planned-changes/otel-forward-port-roadmap.md
index caab88dd213..5ba7e2995d0 100644
--- a/openspec/planned-changes/otel-forward-port-roadmap.md
+++ b/openspec/planned-changes/otel-forward-port-roadmap.md
@@ -13,7 +13,7 @@ Main already contains CAUSEWAY-3975, including instrumentation absent from the s
 | 1 | 3975: foundation reconciliation | **Archived:** [completed change](../changes/archive/2026-09-27-reconcile-main-observation-foundation/proposal.md). Lifecycle cleanup, activation/registry selection, Boot/agent compatibility evidence, threshold behavior, preservation of main instrumentation. | `9b8b66d5b7b`, `e6752f882b5`; compare main's original 3975 implementation |
 | 1b | CAUSEWAY-4096: standardize observation names, metadata and duration-filtering policy | **Archived (2026-10-05):** [standardize observation policy](../changes/archive/2026-10-05-standardize-observation-policy/proposal.md) on branch `CAUSEWAY-4096`. Follow-up to 3975: stable operation names vs contextual names; review bookmark/query-description names, identity attributes, hard-coded duration threshold and missing exported parents. Set policy before adding new semantic names. | Main  [...]
 | 2 | CAUSEWAY-4068 (3975/4068 provenance): classification and correlation | **Archived (2026-10-07):** [classify and correlate observations](../changes/archive/2026-10-07-classify-and-correlate-observations/proposal.md). Foreground/background classification, root interaction ID, fixed execution-mode attribute key. Depends on foundation; adapt to main's interaction layers. | `024bd33128d`, `0325232a3c7`, `3083a51eabd` |
-| 3 | CAUSEWAY-4058: current action context | **Implemented; awaiting review/archive:** [port current action context](../changes/port-current-action-context/proposal.md). Exact-current-action queries and rule-checking state; nested restoration and physical mixin receiver semantics. Can proceed independently of most telemetry work. | `6d037db6609`; account for `f66b8f17a89` |
+| 3 | CAUSEWAY-4058: current action context | **Archived (2026-10-09):** [port current action context](../changes/archive/2026-10-09-port-current-action-context/proposal.md). Exact-current-action queries and rule-checking state; nested restoration and physical mixin receiver semantics. Can proceed independently of most telemetry work. | `6d037db6609`; account for `f66b8f17a89` |
 | 4 | 4062: semantic member names | Planned. Bounded operation categories with domain-facing contextual names; mixed-in property/collection access distinguished from actions. Depends on foundation and naming policy. | `a2ae5877c99`, `d5ecd49a1f3`, `45e01ff6f42` |
 | 5 | 4059 + relevant 4062: Wicket regions/preparation | Planned. Entity-page preparation, region rendering and action prompts; Ajax ancestry, serialization and failure cleanup. The 4062 source touches Wicket as well as core: extract the relevant portions here rather than blindly applying step 4 commits. | `a8264fd1f9b`, relevant parts of `a2ae5877c99` / `45e01ff6f42` |
 | 6 | 4062 + 4067: collection detail and volume | Planned. Table phases, rows, cells, row actions; deliver detail levels, request budgets and collection aggregates with the new detail. Depends on Wicket regions. | `8e5eaab6145`, `036a28a23c2`, `5a1dc2dd17b` |
@@ -127,8 +127,10 @@ Agent bridge completed and archived on 2026-10-07: planning `a7c3d3584e6`, imple
 
 ## Current action context proposal (2026-10-09)
 
-Step 3 is promoted into [port-current-action-context](../changes/port-current-action-context/proposal.md), with design, delta requirements and implementation tasks. It ports `6d037db6609` while incorporating the invoked/domain-facing identity correction from `f66b8f17a89`. The scope includes current-action queries, checked/skipped/unknown rule status, physical mixin receivers, separate contributed identifiers and nested success/failure restoration; it adds no spans. Public guidance and a [...]
+Step 3 is promoted into [port-current-action-context](../changes/archive/2026-10-09-port-current-action-context/proposal.md), with design, delta requirements and implementation tasks. It ports `6d037db6609` while incorporating the invoked/domain-facing identity correction from `f66b8f17a89`. The scope includes current-action queries, checked/skipped/unknown rule status, physical mixin receivers, separate contributed identifiers and nested success/failure restoration; it adds no spans. Pu [...]
 
 Planning baseline: `41d8db75a06` on CAUSEWAY-4068-v4. Refresh main and select a CAUSEWAY-4058 branch before implementation. CAUSEWAY-4065 application-defined spans remains a later dependent forward port; no implementation or validation is claimed by this proposal.
 
-Current action context implemented on CAUSEWAY-4058 after planning commit `9a250065cfa`. All nine tasks completed; [validation evidence](../changes/port-current-action-context/validation.md) records 29 focused tests and 77 executed interact regressions (2 existing disabled tests), documentation rendering and main-specific mixin identity adaptation. Implementation is recorded in the checkpoint commit containing this update; the change remains active, ready for review/archive.
+Current action context implemented on CAUSEWAY-4058 after planning commit `9a250065cfa`. All nine tasks completed; [validation evidence](../changes/archive/2026-10-09-port-current-action-context/validation.md) records 29 focused tests and 77 executed interact regressions (2 existing disabled tests), documentation rendering and main-specific mixin identity adaptation. Implementation is recorded in the checkpoint commit containing this update; the change was subsequently rebased and archiv [...]
+
+Current action context archived on 2026-10-09 after rebase onto main `ce0c8352dee`: rebased planning `5c43e44b085`, implementation `be228adf4ac`. Fresh archive checks passed 29 focused tests and 77 interact regressions (2 existing disabled tests), documentation rendering and strict validation. Requirements are synced to the main specs.
diff --git a/openspec/changes/port-current-action-context/specs/current-action-invocation-context/spec.md b/openspec/specs/current-action-invocation-context/spec.md
similarity index 95%
rename from openspec/changes/port-current-action-context/specs/current-action-invocation-context/spec.md
rename to openspec/specs/current-action-invocation-context/spec.md
index cbf420d97db..5b9b4192bc2 100644
--- a/openspec/changes/port-current-action-context/specs/current-action-invocation-context/spec.md
+++ b/openspec/specs/current-action-invocation-context/spec.md
@@ -1,5 +1,8 @@
-## ADDED Requirements
+# current-action-invocation-context Specification
 
+## Purpose
+Defines read-only current-action queries, aggregate rule-checking status, physical and domain-facing invocation identity, and nested execution restoration.
+## Requirements
 ### Requirement: Provider exposes only the current action execution
 
 InteractionProvider SHALL provide a default `currentActionInvocation()` returning the current ActionInvocation when present. It SHALL return empty for no interaction, no current execution or a current non-action execution, without searching prior or ancestor executions. The query SHALL NOT create executions or require observations to be enabled.
@@ -67,3 +70,4 @@ Queries SHALL reflect the existing interaction execution stack. Nested actions S
 #### Scenario: Interaction completes
 - **WHEN** a mediated top-level action completes and a later interaction or another thread queries current action
 - **THEN** the completed invocation is not exposed as current
+


(causeway) 02/03: CAUSEWAY-4058: expose current action invocation context

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

danhaywood pushed a commit to branch CAUSEWAY-4058
in repository https://gitbox.apache.org/repos/asf/causeway.git

commit be228adf4ac097f64b0634dd81697ebbf359510e
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Fri Oct 9 15:46:49 2026 +0100

    CAUSEWAY-4058: expose current action invocation context
---
 .../index/services/iactn/ActionInvocation.adoc     |  15 ++
 .../index/services/iactn/InteractionProvider.adoc  |  35 ++++
 .../ROOT/pages/2026/4.0.0-M3/otel-howto.adoc       |   7 +
 .../applib/services/iactn/ActionInvocation.java    |  99 +++++++++-
 .../causeway/applib/services/iactn/Execution.java  |   6 +-
 .../applib/services/iactn/InteractionProvider.java | 121 ++++++++++++
 .../services/iactn/InteractionProviderTest.java    | 210 +++++++++++++++++++++
 .../executor/MemberExecutorServiceDefault.java     |  19 +-
 .../executor/MemberExecutorServiceDefaultTest.java |  34 ++++
 .../changes/port-current-action-context/tasks.md   |  18 +-
 .../port-current-action-context/validation.md      |  38 ++++
 .../planned-changes/otel-forward-port-roadmap.md   |   4 +-
 .../interact/WrapperInteraction_1_IntegTest.java   | 198 +++++++++++++++++++
 13 files changed, 789 insertions(+), 15 deletions(-)

diff --git a/antora/components/refguide-index/modules/applib/pages/index/services/iactn/ActionInvocation.adoc b/antora/components/refguide-index/modules/applib/pages/index/services/iactn/ActionInvocation.adoc
index 8982337d577..5ccc0bf7cc7 100644
--- a/antora/components/refguide-index/modules/applib/pages/index/services/iactn/ActionInvocation.adoc
+++ b/antora/components/refguide-index/modules/applib/pages/index/services/iactn/ActionInvocation.adoc
@@ -8,6 +8,21 @@
 ----
 class ActionInvocation {
   ActionInvocation(Interaction interaction, Identifier memberId, Object target, List<Object> args)
+  ActionInvocation(Interaction interaction, Identifier memberId, Object target, List<Object> args, RuleChecking ruleChecking)
+  ActionInvocation(Interaction interaction, Identifier memberId, Identifier domainFacingLogicalMemberIdentifier, Object target, List<Object> args, RuleChecking ruleChecking)
+  RuleChecking getRuleChecking()
+  Identifier getDomainFacingLogicalMemberIdentifier()
+  List<Object> getArgs()
 }
 ----
 
+
+== Invocation identity and rule status
+
+The inherited `target` is the physical method receiver: the domain object for a regular action, or the transient mixin instance for a mixin action.
+`logicalMemberIdentifier` identifies the invoked method; `domainFacingLogicalMemberIdentifier` separately identifies the contributed domain action.
+For regular actions and constructors without a separate domain identifier, both identifiers are the same.
+
+`RuleChecking` has values `CHECKED`, `SKIPPED` and `UNKNOWN`; `isChecked()` returns true only for `CHECKED`.
+The original constructor remains available and defaults to `UNKNOWN`.
+Rule status is aggregate framework execution information and does not change or trigger validation.
diff --git a/antora/components/refguide-index/modules/applib/pages/index/services/iactn/InteractionProvider.adoc b/antora/components/refguide-index/modules/applib/pages/index/services/iactn/InteractionProvider.adoc
index f0761def4a8..62b48dd8bf6 100644
--- a/antora/components/refguide-index/modules/applib/pages/index/services/iactn/InteractionProvider.adoc
+++ b/antora/components/refguide-index/modules/applib/pages/index/services/iactn/InteractionProvider.adoc
@@ -14,6 +14,11 @@ interface InteractionProvider {
   boolean isInInteraction()     // <.>
   Optional<Interaction> currentInteraction()     // <.>
   Optional<InteractionContext> currentInteractionContext()     // <.>
+  Optional<ActionInvocation> currentActionInvocation()
+  boolean isCurrentActionInvocation(Object target, Identifier identifier)
+  boolean isCurrentActionInvocation(Object target, String memberName)
+  boolean isCurrentActionInvocation(Object target, Identifier identifier, ActionInvocation.RuleChecking ruleChecking)
+  boolean isCurrentActionInvocation(Object target, String memberName, ActionInvocation.RuleChecking ruleChecking)
   Optional<UUID> getInteractionId()     // <.>
   int getInteractionLayerCount()     // <.>
   Interaction currentInteractionElseFail()
@@ -73,3 +78,33 @@ Unique id of the current request- or test-scoped xref:refguide:applib:index/serv
 === getInteractionLayerCount()
 
 interaction-layer-stack size
+
+== Current action invocation
+
+`currentActionInvocation()` returns the current framework action execution, or empty when there is no action execution (including during a nested property edit).
+Nested wrapper calls temporarily expose the child invocation and restore the parent when control returns, including after failure.
+
+An action can retain defensive validation for direct Java callers while avoiding a duplicate check after framework validation:
+
+[source,java]
+----
+public void updateName(String name) {
+    if (!interactionService.isCurrentActionInvocation(
+            this, "updateName", ActionInvocation.RuleChecking.CHECKED)) {
+        // Perform the defensive checks needed for direct or skip-rules callers.
+        checkName(name);
+    }
+    this.name = name;
+}
+----
+
+Receiver matching uses Java identity (`==`), not entity equality.
+The full `Identifier` overload distinguishes parameter signatures; the member-name form does not distinguish same-name overloads.
+For a mixin implementation, use the mixin receiver (`this`) and its invoked method name, typically `"act"`.
+The invocation's separate `domainFacingLogicalMemberIdentifier` identifies the contributed domain action.
+
+`CHECKED` describes the framework's rule-checking mode, `SKIPPED` an intentionally unchecked invocation, and `UNKNOWN` an invocation constructed without that information.
+Presence of an invocation alone does not imply that rules were checked, and the aggregate status is not an inventory of individual supporting methods evaluated.
+
+Direct Java calls create no execution frames.
+If such a call occurs inside a framework action, queries can still see that outer action; this API does not inspect arbitrary Java stack frames.
diff --git a/antora/components/relnotes/modules/ROOT/pages/2026/4.0.0-M3/otel-howto.adoc b/antora/components/relnotes/modules/ROOT/pages/2026/4.0.0-M3/otel-howto.adoc
index 8bab124d42e..0445485ab8e 100644
--- a/antora/components/relnotes/modules/ROOT/pages/2026/4.0.0-M3/otel-howto.adoc
+++ b/antora/components/relnotes/modules/ROOT/pages/2026/4.0.0-M3/otel-howto.adoc
@@ -333,6 +333,13 @@ A custom registry must configure its tracing handlers to use the same single or
 
 
 
+== Current action context (CAUSEWAY-4058)
+
+Applications can inspect the currently executing framework action through `InteractionProvider.currentActionInvocation()` and match its receiver, invoked identifier and rule-checking status with `isCurrentActionInvocation(...)`.
+Mixin invocations also expose their domain-facing action identifier separately from the implementation method identifier.
+This context API works with observations disabled and adds no spans; it provides the invocation information needed for the later application-defined spans capability.
+See xref:refguide:applib:index/services/iactn/InteractionProvider.adoc[InteractionProvider] for examples and direct Java-call limitations.
+
 == Further reading
 
 * https://spring.io/blog/2025/11/18/opentelemetry-with-spring-boot[OpenTelemetry with Spring Boot]
diff --git a/api/applib/src/main/java/org/apache/causeway/applib/services/iactn/ActionInvocation.java b/api/applib/src/main/java/org/apache/causeway/applib/services/iactn/ActionInvocation.java
index 65a866c2ded..73e7f1b51a1 100644
--- a/api/applib/src/main/java/org/apache/causeway/applib/services/iactn/ActionInvocation.java
+++ b/api/applib/src/main/java/org/apache/causeway/applib/services/iactn/ActionInvocation.java
@@ -26,23 +26,120 @@
 import org.apache.causeway.schema.ixn.v2.ActionInvocationDto;
 
 import lombok.Getter;
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
 
 /**
+ * Represents an action method execution in an {@link Interaction} execution graph.
+ *
  * @since 1.x {@index}
  */
 public class ActionInvocation
 extends Execution<ActionInvocationDto, ActionDomainEvent<?>> {
 
+    /**
+     * Whether Causeway's visibility, usability and validity rules were checked
+     * before this action invocation.
+     *
+     * @since 4.0.0
+     */
+    @RequiredArgsConstructor
+    public enum RuleChecking {
+        /** Causeway checked visibility, usability and validity rules. */
+        CHECKED(true),
+        /** Causeway intentionally skipped rule checking. */
+        SKIPPED(false),
+        /** Rule-checking status was not supplied when the invocation was created. */
+        UNKNOWN(false);
+
+        private final boolean checked;
+
+        /**
+         * Reports whether rules were checked.
+         *
+         * @return whether Causeway checked rules before invoking the action
+         */
+        public boolean isChecked() {
+            return checked;
+        }
+    }
+
     @Getter
     private final List<Object> args;
 
+    /**
+     * The domain-facing logical action identifier.
+     *
+     * <p>
+     * This differs from {@link #getLogicalMemberIdentifier()} for a mixin action:
+     * the inherited identifier describes the mixin implementation method, typically
+     * {@code act}, while this identifier describes the contributed domain action.
+     * </p>
+     *
+     * @since 4.0.0
+     */
+    @Getter
+    private final Identifier domainFacingLogicalMemberIdentifier;
+
+    @Getter
+    private final RuleChecking ruleChecking;
+
+    /**
+     * Creates an action invocation whose rule-checking status is not known,
+     * for example when reconstructing an invocation from serialized history.
+     *
+     * @param interaction owning interaction
+     * @param memberId logical action identifier
+     * @param target action method receiver
+     * @param args action arguments
+     */
     public ActionInvocation(
             final Interaction interaction,
             final Identifier memberId,
             final Object target,
             final List<Object> args) {
+        this(interaction, memberId, memberId, target, args, RuleChecking.UNKNOWN);
+    }
+
+    /**
+     * Creates an action invocation with explicit rule-checking status.
+     *
+     * @param interaction owning interaction
+     * @param memberId logical action identifier
+     * @param target action method receiver
+     * @param args action arguments
+     * @param ruleChecking whether Causeway checked rules before invoking the action
+     */
+    public ActionInvocation(
+            final Interaction interaction,
+            final Identifier memberId,
+            final Object target,
+            final List<Object> args,
+            final @NonNull RuleChecking ruleChecking) {
+        this(interaction, memberId, memberId, target, args, ruleChecking);
+    }
+
+    /**
+     * Creates an action invocation with separate invoked-method and domain-facing identifiers.
+     *
+     * @param interaction owning interaction
+     * @param memberId logical identifier of the method invoked on {@code target}
+     * @param domainFacingLogicalMemberIdentifier domain-facing logical action identifier
+     * @param target action method receiver
+     * @param args action arguments
+     * @param ruleChecking whether Causeway checked rules before invoking the action
+     */
+    public ActionInvocation(
+            final Interaction interaction,
+            final Identifier memberId,
+            final @NonNull Identifier domainFacingLogicalMemberIdentifier,
+            final Object target,
+            final List<Object> args,
+            final @NonNull RuleChecking ruleChecking) {
         super(interaction, InteractionType.ACTION_INVOCATION, memberId, target);
         this.args = args;
+        this.domainFacingLogicalMemberIdentifier = domainFacingLogicalMemberIdentifier;
+        this.ruleChecking = ruleChecking;
     }
- 
+    // ...
 }
diff --git a/api/applib/src/main/java/org/apache/causeway/applib/services/iactn/Execution.java b/api/applib/src/main/java/org/apache/causeway/applib/services/iactn/Execution.java
index 057ee1fbed5..19d2092e938 100644
--- a/api/applib/src/main/java/org/apache/causeway/applib/services/iactn/Execution.java
+++ b/api/applib/src/main/java/org/apache/causeway/applib/services/iactn/Execution.java
@@ -75,11 +75,11 @@ public abstract class Execution<T extends MemberExecutionDto, E extends Abstract
     private final Identifier logicalMemberIdentifier;
 
     /**
-     * The target of the action invocation.
+     * The object on which the action or property method is invoked.
      *
      * <p>
-     * If this interaction is for a mixin action, then will be the mixed-in
-     * target (not the transient mixin itself).
+     * For a mixin action this is the transient mixin instance, not the
+     * mixed-in domain object.
      * </p>
      */
     @Getter
diff --git a/api/applib/src/main/java/org/apache/causeway/applib/services/iactn/InteractionProvider.java b/api/applib/src/main/java/org/apache/causeway/applib/services/iactn/InteractionProvider.java
index bb4e0b9a5eb..de23364984b 100644
--- a/api/applib/src/main/java/org/apache/causeway/applib/services/iactn/InteractionProvider.java
+++ b/api/applib/src/main/java/org/apache/causeway/applib/services/iactn/InteractionProvider.java
@@ -21,6 +21,9 @@
 import java.util.Optional;
 import java.util.UUID;
 
+import org.apache.causeway.applib.Identifier;
+import lombok.NonNull;
+
 import org.apache.causeway.commons.internal.debug._Probe;
 import org.apache.causeway.commons.internal.exceptions._Exceptions;
 
@@ -56,6 +59,124 @@ public interface InteractionProvider {
      */
     Optional<InteractionContext> currentInteractionContext();
 
+    /**
+     * The current action invocation, if the current interaction execution is an action.
+     *
+     * <p>
+     * This describes execution through Causeway's action invocation machinery.
+     * Inspect the returned {@link ActionInvocation}'s rule-checking status to determine
+     * whether visibility, usability and validity rules were checked or skipped.
+     * Only the current framework execution is considered; a nested property
+     * edit does not expose its enclosing action. Direct Java calls do not create
+     * execution frames, so an outer framework invocation may remain visible.
+     * </p>
+     *
+     * @return the current action invocation, if any
+     * @since 4.0.0
+     */
+    default Optional<ActionInvocation> currentActionInvocation() {
+        return currentInteraction()
+                .map(Interaction::getCurrentExecution)
+                .filter(ActionInvocation.class::isInstance)
+                .map(ActionInvocation.class::cast);
+    }
+
+    /**
+     * Whether the current action invocation is for the identical {@code target} and
+     * has the supplied logical member {@code identifier}.
+     *
+     * <p>
+     * Target comparison deliberately uses object identity rather than
+     * {@link Object#equals(Object)}.
+     * For a mixin action the target is the transient mixin instance on which
+     * {@code act} is invoked, not the mixed-in domain object.
+     * </p>
+     *
+     * @param target action method receiver
+     * @param identifier full logical action identifier
+     * @return whether the target and identifier match the current action invocation
+     * @since 4.0.0
+     */
+    default boolean isCurrentActionInvocation(
+            final @NonNull Object target,
+            final @NonNull Identifier identifier) {
+        return currentActionInvocation()
+                .filter(actionInvocation -> actionInvocation.getTarget() == target)
+                .map(ActionInvocation::getLogicalMemberIdentifier)
+                .filter(identifier::equals)
+                .isPresent();
+    }
+
+    /**
+     * Whether the current action invocation is for the identical {@code target} and
+     * has the supplied logical {@code memberName}.
+     *
+     * <p>
+     * This convenience form does not distinguish overloaded action identifiers.
+     * Use {@link #isCurrentActionInvocation(Object, Identifier)} when the full action
+     * identity is required.
+     * For a mixin action, callers typically supply {@code this} as the target and
+     * {@code "act"} as the member name.
+     * </p>
+     *
+     * @param target action method receiver
+     * @param memberName logical member name
+     * @return whether the target and member name match the current action invocation
+     * @since 4.0.0
+     */
+    default boolean isCurrentActionInvocation(
+            final @NonNull Object target,
+            final @NonNull String memberName) {
+        return currentActionInvocation()
+                .filter(actionInvocation -> actionInvocation.getTarget() == target)
+                .map(ActionInvocation::getLogicalMemberIdentifier)
+                .map(Identifier::memberLogicalName)
+                .filter(memberName::equals)
+                .isPresent();
+    }
+
+    /**
+     * Whether the current action invocation matches the supplied target,
+     * identifier and rule-checking status.
+     *
+     * @param target action method receiver
+     * @param identifier full logical action identifier
+     * @param ruleChecking expected rule-checking status
+     * @return whether all supplied characteristics match the current action invocation
+     * @since 4.0.0
+     */
+    default boolean isCurrentActionInvocation(
+            final @NonNull Object target,
+            final @NonNull Identifier identifier,
+            final @NonNull ActionInvocation.RuleChecking ruleChecking) {
+        return isCurrentActionInvocation(target, identifier)
+                && currentActionInvocation()
+                        .map(ActionInvocation::getRuleChecking)
+                        .filter(ruleChecking::equals)
+                        .isPresent();
+    }
+
+    /**
+     * Whether the current action invocation matches the supplied target,
+     * logical member name and rule-checking status.
+     *
+     * @param target action method receiver
+     * @param memberName logical member name
+     * @param ruleChecking expected rule-checking status
+     * @return whether all supplied characteristics match the current action invocation
+     * @since 4.0.0
+     */
+    default boolean isCurrentActionInvocation(
+            final @NonNull Object target,
+            final @NonNull String memberName,
+            final @NonNull ActionInvocation.RuleChecking ruleChecking) {
+        return isCurrentActionInvocation(target, memberName)
+                && currentActionInvocation()
+                        .map(ActionInvocation::getRuleChecking)
+                        .filter(ruleChecking::equals)
+                        .isPresent();
+    }
+
     /**
      * Unique id of the current request- or test-scoped {@link Interaction}.
      */
diff --git a/api/applib/src/test/java/org/apache/causeway/applib/services/iactn/InteractionProviderTest.java b/api/applib/src/test/java/org/apache/causeway/applib/services/iactn/InteractionProviderTest.java
new file mode 100644
index 00000000000..9a07ef8e0da
--- /dev/null
+++ b/api/applib/src/test/java/org/apache/causeway/applib/services/iactn/InteractionProviderTest.java
@@ -0,0 +1,210 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.causeway.applib.services.iactn;
+
+import java.lang.reflect.Proxy;
+import java.util.Collections;
+import java.util.Optional;
+import java.util.UUID;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.apache.causeway.applib.Identifier;
+import org.apache.causeway.applib.id.LogicalType;
+import org.apache.causeway.applib.services.iactn.ActionInvocation.RuleChecking;
+
+class InteractionProviderTest {
+
+    private static class Customer {}
+    private static class Customer_updateName {}
+
+    private final Object target = new String("target");
+    private final Identifier actionIdentifier = Identifier.actionIdentifier(
+            LogicalType.fqcn(Customer.class), "invoice", String.class);
+
+    @Test
+    void currentActionInvocation_whenThereIsNoInteraction() {
+        var provider = providerFor(Optional.empty());
+
+        assertTrue(provider.currentActionInvocation().isEmpty());
+        assertFalse(provider.isCurrentActionInvocation(target, actionIdentifier));
+        assertFalse(provider.isCurrentActionInvocation(target, "invoice"));
+    }
+
+    @Test
+    void currentActionInvocation_whenCurrentExecutionIsNotAnAction() {
+        var interaction = interactionWith(new PropertyEdit(
+                null,
+                Identifier.propertyIdentifier(LogicalType.fqcn(Customer.class), "name"),
+                target,
+                "new name"));
+        var provider = providerFor(Optional.of(interaction));
+
+        assertTrue(provider.currentActionInvocation().isEmpty());
+    }
+
+    @Test
+    void currentActionInvocation_whenCurrentExecutionIsAnAction() {
+        var actionInvocation = new ActionInvocation(
+                null, actionIdentifier, target, Collections.singletonList("arg"), RuleChecking.CHECKED);
+        var provider = providerFor(Optional.of(interactionWith(actionInvocation)));
+
+        assertSame(actionInvocation, provider.currentActionInvocation().orElseThrow());
+        assertSame(RuleChecking.CHECKED, actionInvocation.getRuleChecking());
+        assertTrue(provider.isCurrentActionInvocation(target, actionIdentifier));
+        assertTrue(provider.isCurrentActionInvocation(target, "invoice"));
+        assertTrue(provider.isCurrentActionInvocation(target, actionIdentifier, RuleChecking.CHECKED));
+        assertTrue(provider.isCurrentActionInvocation(target, "invoice", RuleChecking.CHECKED));
+        assertFalse(provider.isCurrentActionInvocation(target, actionIdentifier, RuleChecking.SKIPPED));
+        assertFalse(provider.isCurrentActionInvocation(target, "invoice", RuleChecking.SKIPPED));
+    }
+
+    @Test
+    void currentActionInvocation_matchesInvokedMixinMethodRatherThanDomainFacingIdentity() {
+        final Identifier domainFacingIdentifier = Identifier.actionIdentifier(
+                LogicalType.fqcn(Customer.class), "updateName", String.class);
+        final Identifier invokedMemberIdentifier = Identifier.actionIdentifier(
+                LogicalType.fqcn(Customer_updateName.class), "act", String.class);
+        var actionInvocation = new ActionInvocation(
+                null,
+                invokedMemberIdentifier,
+                domainFacingIdentifier,
+                target,
+                Collections.singletonList("arg"),
+                RuleChecking.CHECKED);
+        var provider = providerFor(Optional.of(interactionWith(actionInvocation)));
+
+        assertSame(invokedMemberIdentifier, actionInvocation.getLogicalMemberIdentifier());
+        assertSame(domainFacingIdentifier,
+                actionInvocation.getDomainFacingLogicalMemberIdentifier());
+        assertTrue(provider.isCurrentActionInvocation(target, invokedMemberIdentifier));
+        assertTrue(provider.isCurrentActionInvocation(target, "act"));
+        assertFalse(provider.isCurrentActionInvocation(target, domainFacingIdentifier));
+        assertFalse(provider.isCurrentActionInvocation(target, "updateName"));
+    }
+
+    @Test
+    void currentActionInvocation_compatibilityConstructorHasUnknownRuleChecking() {
+        var actionInvocation = new ActionInvocation(
+                null, actionIdentifier, target, Collections.emptyList());
+
+        assertSame(RuleChecking.UNKNOWN, actionInvocation.getRuleChecking());
+    }
+
+    @Test
+    void isCurrentActionInvocation_requiresMatchingIdentifier() {
+        var actionInvocation = new ActionInvocation(
+                null, actionIdentifier, target, Collections.emptyList(), RuleChecking.SKIPPED);
+        var provider = providerFor(Optional.of(interactionWith(actionInvocation)));
+        var otherIdentifier = Identifier.actionIdentifier(
+                LogicalType.fqcn(Customer.class), "collect", String.class);
+
+        assertFalse(provider.isCurrentActionInvocation(target, otherIdentifier));
+        assertFalse(provider.isCurrentActionInvocation(target, "collect"));
+        assertTrue(provider.isCurrentActionInvocation(target, actionIdentifier, RuleChecking.SKIPPED));
+        assertTrue(provider.isCurrentActionInvocation(target, "invoice", RuleChecking.SKIPPED));
+    }
+
+    @Test
+    void isCurrentActionInvocation_comparesTargetByIdentity() {
+        var equalButNotIdenticalTarget = new String("target");
+        var actionInvocation = new ActionInvocation(
+                null, actionIdentifier, target, Collections.emptyList(), RuleChecking.CHECKED);
+        var provider = providerFor(Optional.of(interactionWith(actionInvocation)));
+
+        assertTrue(target.equals(equalButNotIdenticalTarget));
+        assertFalse(provider.isCurrentActionInvocation(equalButNotIdenticalTarget, actionIdentifier));
+        assertFalse(provider.isCurrentActionInvocation(equalButNotIdenticalTarget, "invoice"));
+    }
+
+    @Test
+    void emptyExecutionAndOverloads() {
+        var provider = providerFor(Optional.of(interactionWith(null)));
+        assertTrue(provider.currentActionInvocation().isEmpty());
+        var invocation = new ActionInvocation(null, actionIdentifier, target, Collections.emptyList());
+        provider = providerFor(Optional.of(interactionWith(invocation)));
+        assertFalse(provider.isCurrentActionInvocation(target,
+                Identifier.actionIdentifier(LogicalType.fqcn(Customer.class), "invoice", Integer.class)));
+        assertTrue(provider.isCurrentActionInvocation(target, "invoice"));
+        assertSame(actionIdentifier, invocation.getDomainFacingLogicalMemberIdentifier());
+        assertFalse(RuleChecking.UNKNOWN.isChecked());
+        assertFalse(RuleChecking.SKIPPED.isChecked());
+        assertTrue(RuleChecking.CHECKED.isChecked());
+    }
+
+    @Test
+    void identityChecksDoNotCallEquals() {
+        var receiver = new Object() {
+            @Override public boolean equals(Object other) { throw new AssertionError("equals must not be called"); }
+        };
+        var provider = providerFor(Optional.of(interactionWith(
+                new ActionInvocation(null, actionIdentifier, receiver, Collections.emptyList()))));
+        assertTrue(provider.isCurrentActionInvocation(receiver, actionIdentifier));
+        assertFalse(provider.isCurrentActionInvocation(new Object(), actionIdentifier));
+    }
+
+    @Test
+    void rejectsNullQueryArgumentsAndExplicitState() {
+        var provider = providerFor(Optional.empty());
+        assertThrows(NullPointerException.class, () -> provider.isCurrentActionInvocation(null, "invoice"));
+        assertThrows(NullPointerException.class, () -> provider.isCurrentActionInvocation(target, (String) null));
+        assertThrows(NullPointerException.class, () -> provider.isCurrentActionInvocation(target, (Identifier) null));
+        assertThrows(NullPointerException.class, () -> provider.isCurrentActionInvocation(target, "invoice", null));
+        assertThrows(NullPointerException.class, () -> provider.isCurrentActionInvocation(target, actionIdentifier, null));
+        assertThrows(NullPointerException.class, () -> new ActionInvocation(null, actionIdentifier, target, Collections.emptyList(), null));
+        assertThrows(NullPointerException.class, () -> new ActionInvocation(null, actionIdentifier, null, target, Collections.emptyList(), RuleChecking.CHECKED));
+    }
+
+    private static Interaction interactionWith(final Execution<?, ?> currentExecution) {
+        return (Interaction) Proxy.newProxyInstance(
+                Interaction.class.getClassLoader(),
+                new Class<?>[] { Interaction.class },
+                (proxy, method, args) -> "getCurrentExecution".equals(method.getName())
+                        ? currentExecution
+                        : defaultValue(method.getReturnType()));
+    }
+
+    private static InteractionProvider providerFor(final Optional<Interaction> interaction) {
+        return new InteractionProvider() {
+            @Override public boolean isInInteraction() { return interaction.isPresent(); }
+            @Override public Optional<Interaction> currentInteraction() { return interaction; }
+            @Override public Optional<InteractionContext> currentInteractionContext() { return Optional.empty(); }
+            @Override public Optional<UUID> getInteractionId() { return Optional.empty(); }
+            @Override public int getInteractionLayerCount() { return interaction.isPresent() ? 1 : 0; }
+        };
+    }
+
+    private static Object defaultValue(final Class<?> returnType) {
+        if (!returnType.isPrimitive()) {
+            return null;
+        }
+        if (returnType == boolean.class) {
+            return false;
+        }
+        if (returnType == char.class) {
+            return '\0';
+        }
+        return 0;
+    }
+}
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 439c077f153..2ddc9bf5514 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
@@ -184,7 +184,10 @@ private ManagedObject invokeActionInternally(
                 .collect(_Lists.toUnmodifiable());
 
         var actionInvocation = new ActionInvocation(
-        		interactionCarrier.interaction(), actionId, targetPojo, argumentPojos);
+                interactionCarrier.interaction(), actionId, domainFacingActionIdentifier(head, owningAction),
+                targetPojo, argumentPojos, interactionInitiatedBy.isUser()
+                        ? ActionInvocation.RuleChecking.CHECKED
+                        : ActionInvocation.RuleChecking.SKIPPED);
 
         // sets up startedAt and completedAt on the execution, also manages the execution call graph
         execute(interactionCarrier, actionExecutor, actionInvocation);
@@ -407,6 +410,20 @@ private void setCommandResultIfEntityScalar(
                 .ifPresent(bookmark -> command.updater().setResult(Try.success(bookmark)));
     }
 
+    /** Keeps physical invocation identity intact while exposing a contributed action's name. */
+    static Identifier domainFacingActionIdentifier(final InteractionHead head, final ObjectAction owningAction) {
+        var invokedId = owningAction.getFeatureIdentifier();
+        if (!owningAction.isDeclaredOnMixin()) {
+            return invokedId;
+        }
+        // Main carries the mixee/mixin pair instead of maintenance's managed
+        // action head. Use the strategy that constructs contributed members.
+        var contributedName = owningAction.getProgrammingModel().mixinNamingStrategy()
+                .memberId(invokedId.logicalType().correspondingClass());
+        return Identifier.actionIdentifier(head.owner().objSpec().logicalType(),
+                contributedName, invokedId.memberParameterClassNames());
+    }
+
     private ManagedObject resultFilteredHonoringVisibility(
             final ManagedObject resultAdapter,
             final InteractionInitiatedBy interactionInitiatedBy) {
diff --git a/core/runtimeservices/src/test/java/org/apache/causeway/core/runtimeservices/executor/MemberExecutorServiceDefaultTest.java b/core/runtimeservices/src/test/java/org/apache/causeway/core/runtimeservices/executor/MemberExecutorServiceDefaultTest.java
index c727fd57b42..608fc618282 100644
--- a/core/runtimeservices/src/test/java/org/apache/causeway/core/runtimeservices/executor/MemberExecutorServiceDefaultTest.java
+++ b/core/runtimeservices/src/test/java/org/apache/causeway/core/runtimeservices/executor/MemberExecutorServiceDefaultTest.java
@@ -52,6 +52,40 @@
 
 class MemberExecutorServiceDefaultTest {
 
+    @Test
+    void currentContextPreservesRegularIdentifier() {
+        var action = mock(ObjectAction.class);
+        var identifier = org.apache.causeway.applib.Identifier.actionIdentifier(
+                org.apache.causeway.applib.id.LogicalType.fqcn(SuppressedTarget.class), "update", String.class);
+        when(action.getFeatureIdentifier()).thenReturn(identifier);
+        assertThat(MemberExecutorServiceDefault.domainFacingActionIdentifier(null, action)).isSameAs(identifier);
+    }
+
+    @Test
+    void domainContextUsesMainMixinNamingAndPreservesParameters() {
+        var action = mock(ObjectAction.class);
+        var owner = managedObject(new SuppressedTarget());
+        var ownerSpec = owner.objSpec();
+        var ownerType = org.apache.causeway.applib.id.LogicalType.fqcn(SuppressedTarget.class);
+        var invokedType = org.apache.causeway.applib.id.LogicalType.fqcn(MixinReceiver.class);
+        var invokedId = org.apache.causeway.applib.Identifier.actionIdentifier(invokedType, "act", String.class);
+        var programmingModel = mock(org.apache.causeway.core.metamodel.progmodel.ProgrammingModel.class);
+        var naming = mock(org.apache.causeway.core.metamodel.progmodel.ProgrammingModel.MixinNamingStrategy.class);
+        when(action.getFeatureIdentifier()).thenReturn(invokedId);
+        when(action.isDeclaredOnMixin()).thenReturn(true);
+        when(action.getProgrammingModel()).thenReturn(programmingModel);
+        when(programmingModel.mixinNamingStrategy()).thenReturn(naming);
+        when(naming.memberId(MixinReceiver.class)).thenReturn("rename");
+        when(ownerSpec.logicalType()).thenReturn(ownerType);
+        var head = mock(InteractionHead.class);
+        when(head.owner()).thenReturn(owner);
+        assertThat(MemberExecutorServiceDefault.domainFacingActionIdentifier(head, action))
+                .isEqualTo(org.apache.causeway.applib.Identifier.actionIdentifier(ownerType, "rename", String.class));
+        assertThat(action.getFeatureIdentifier()).isSameAs(invokedId);
+    }
+
+    private static class MixinReceiver {}
+
     @Test
     void recordingAwareSafeActionOnSuppressedTargetBypassesCommandPreparation() {
         var commandPublisherProvider = commandPublisherProvider();
diff --git a/openspec/changes/port-current-action-context/tasks.md b/openspec/changes/port-current-action-context/tasks.md
index db53fac6c01..508d56f4289 100644
--- a/openspec/changes/port-current-action-context/tasks.md
+++ b/openspec/changes/port-current-action-context/tasks.md
@@ -1,17 +1,17 @@
 ## 1. Public invocation API
 
-- [ ] 1.1 Add InteractionProvider default typed accessor and identity/identifier/member-name predicates, including expected-status variants and documented null/overload semantics.
-- [ ] 1.2 Add RuleChecking, compatible constructors and separate domain-facing identifier to ActionInvocation; correct Execution receiver Javadoc and use main release @since annotations.
-- [ ] 1.3 Add applib tests for absent/non-action execution, identity without equals, full identifier versus name, null arguments, statuses, legacy defaults and regular/mixin identifier separation.
+- [x] 1.1 Add InteractionProvider default typed accessor and identity/identifier/member-name predicates, including expected-status variants and documented null/overload semantics.
+- [x] 1.2 Add RuleChecking, compatible constructors and separate domain-facing identifier to ActionInvocation; correct Execution receiver Javadoc and use main release @since annotations.
+- [x] 1.3 Add applib tests for absent/non-action execution, identity without equals, full identifier versus name, null arguments, statuses, legacy defaults and regular/mixin identifier separation.
 
 ## 2. Main runtime integration
 
-- [ ] 2.1 Verify main USER/FRAMEWORK/PASS_THROUGH rule paths, record CHECKED/SKIPPED in MemberExecutorServiceDefault and derive domain-facing action identity without changing the invoked identifier.
-- [ ] 2.2 Extend wrapper integration regressions for normal/skip-rules actions and mixin physical receiver/contributed identifier behavior, preserving existing publishing/DTO results.
-- [ ] 2.3 Verify nested success/failure and property-edit restoration, cleanup after completion and thread/interaction isolation; make only reproduced lifecycle fixes if necessary.
+- [x] 2.1 Verify main USER/FRAMEWORK/PASS_THROUGH rule paths, record CHECKED/SKIPPED in MemberExecutorServiceDefault and derive domain-facing action identity without changing the invoked identifier.
+- [x] 2.2 Extend wrapper integration regressions for normal/skip-rules actions and mixin physical receiver/contributed identifier behavior, preserving existing publishing/DTO results.
+- [x] 2.3 Verify nested success/failure and property-edit restoration, cleanup after completion and thread/interaction isolation; make only reproduced lifecycle fixes if necessary.
 
 ## 3. Documentation and validation
 
-- [ ] 3.1 Update InteractionProvider/ActionInvocation public guidance with exact checked-action examples, UNKNOWN, overloads and direct Java-call limitations.
-- [ ] 3.2 Add a concise supporting-capability explanation to the M3 otel-howto, making clear that the context API adds no spans and supports the later application-span forward port.
-- [ ] 3.3 Run focused applib/runtime tests and applicable interact regression profile; render changed documentation and record commands, versions, results and remaining limitations in validation.md.
+- [x] 3.1 Update InteractionProvider/ActionInvocation public guidance with exact checked-action examples, UNKNOWN, overloads and direct Java-call limitations.
+- [x] 3.2 Add a concise supporting-capability explanation to the M3 otel-howto, making clear that the context API adds no spans and supports the later application-span forward port.
+- [x] 3.3 Run focused applib/runtime tests and applicable interact regression profile; render changed documentation and record commands, versions, results and remaining limitations in validation.md.
diff --git a/openspec/changes/port-current-action-context/validation.md b/openspec/changes/port-current-action-context/validation.md
new file mode 100644
index 00000000000..5a618480852
--- /dev/null
+++ b/openspec/changes/port-current-action-context/validation.md
@@ -0,0 +1,38 @@
+# Validation
+
+2026-10-09 on CAUSEWAY-4058, planning checkpoint `9a250065cfa` (based on local `41d8db75a06`). Java 25.0.4, Maven 3.9.13, main Boot 4.2.0-M1. Implementation is recorded in the checkpoint commit containing this validation evidence.
+
+## Implementation adaptation
+
+Main uses InteractionHead as a mixee/mixin pair, rather than maintenance's ActionInteractionHead subclass. Domain-facing identity is constructed using the owning domain specification's logical type, the programming model's existing mixin naming strategy and the invoked identifier's parameter signature. Unit tests cover custom naming and parameter retention; a real wrapper regression compares the result to the contributed action's metamodel identifier. The invoked identifier remains unchanged.
+
+Wrapper rule controls map normal execution to USER and skip-rules to FRAMEWORK in DomainObjectInvocationHandler. These become CHECKED and SKIPPED; PASS_THROUGH still returns before ActionInvocation creation. Legacy constructors report UNKNOWN. No rule-processing or carrier lifecycle changes were needed.
+
+## Checks
+
+Focused reactor command:
+
+```bash
+JAVA_HOME=/Users/danhaywood/.sdkman/candidates/java/25.0.4-tem \
+  mvn -o -pl api/applib,core/runtimeservices -am \
+  -Dtest=InteractionProviderTest,MemberExecutorServiceDefaultTest,InteractionServiceObservationTest \
+  -Dsurefire.failIfNoSpecifiedTests=false -Dmaven.source.skip=true -Dmaven.javadoc.skip=true install
+```
+
+Passed 29 tests: 10 provider tests, 12 executor tests and 7 observation lifecycle tests. Checks include empty and non-action execution, identity without equals, full signatures versus names, null arguments, checked/skipped/unknown state and separate invoked/domain-facing identifiers.
+
+Interact regression command:
+
+```bash
+JAVA_HOME=/Users/danhaywood/.sdkman/candidates/java/25.0.4-tem \
+  mvn -o -f regressiontests/pom.xml -pl interact -am \
+  -Dmaven.source.skip=true -Dmaven.javadoc.skip=true test
+```
+
+Passed: 79 reported, 77 executed, 2 existing disabled tests, no failures/errors. Includes nine WrapperInteraction_1_IntegTest tests: real normal/skip-rules wrapper invocations, regular/mixin identity, nested child success/failure, property-edit exclusion, restoration, thread isolation, top-level failure cleanup, later interaction and direct Java calls. Existing action/property/wrapper and publishing-related interaction regressions also passed. Expected exceptions in the failure fixture a [...]
+
+Rendered all three changed AsciiDoc pages with the locally installed Asciidoctor: InteractionProvider reference, ActionInvocation reference and M3 otel-howto. Strict OpenSpec change validation and git diff whitespace validation passed.
+
+## Limits
+
+This additive invocation API emits no new spans and requires no observation profile. No interactive browser or external exporter verification applies. Direct Java calls do not introduce framework execution frames and can still see an enclosing invocation. RuleChecking is aggregate mode information, not individual rule provenance. This implementation is based on the local preceding feature branch; remote main was not fetched or merged during apply.
diff --git a/openspec/planned-changes/otel-forward-port-roadmap.md b/openspec/planned-changes/otel-forward-port-roadmap.md
index b07db600818..caab88dd213 100644
--- a/openspec/planned-changes/otel-forward-port-roadmap.md
+++ b/openspec/planned-changes/otel-forward-port-roadmap.md
@@ -13,7 +13,7 @@ Main already contains CAUSEWAY-3975, including instrumentation absent from the s
 | 1 | 3975: foundation reconciliation | **Archived:** [completed change](../changes/archive/2026-09-27-reconcile-main-observation-foundation/proposal.md). Lifecycle cleanup, activation/registry selection, Boot/agent compatibility evidence, threshold behavior, preservation of main instrumentation. | `9b8b66d5b7b`, `e6752f882b5`; compare main's original 3975 implementation |
 | 1b | CAUSEWAY-4096: standardize observation names, metadata and duration-filtering policy | **Archived (2026-10-05):** [standardize observation policy](../changes/archive/2026-10-05-standardize-observation-policy/proposal.md) on branch `CAUSEWAY-4096`. Follow-up to 3975: stable operation names vs contextual names; review bookmark/query-description names, identity attributes, hard-coded duration threshold and missing exported parents. Set policy before adding new semantic names. | Main  [...]
 | 2 | CAUSEWAY-4068 (3975/4068 provenance): classification and correlation | **Archived (2026-10-07):** [classify and correlate observations](../changes/archive/2026-10-07-classify-and-correlate-observations/proposal.md). Foreground/background classification, root interaction ID, fixed execution-mode attribute key. Depends on foundation; adapt to main's interaction layers. | `024bd33128d`, `0325232a3c7`, `3083a51eabd` |
-| 3 | CAUSEWAY-4058: current action context | **Proposed:** [port current action context](../changes/port-current-action-context/proposal.md). Exact-current-action queries and rule-checking state; nested restoration and physical mixin receiver semantics. Can proceed independently of most telemetry work. | `6d037db6609`; account for `f66b8f17a89` |
+| 3 | CAUSEWAY-4058: current action context | **Implemented; awaiting review/archive:** [port current action context](../changes/port-current-action-context/proposal.md). Exact-current-action queries and rule-checking state; nested restoration and physical mixin receiver semantics. Can proceed independently of most telemetry work. | `6d037db6609`; account for `f66b8f17a89` |
 | 4 | 4062: semantic member names | Planned. Bounded operation categories with domain-facing contextual names; mixed-in property/collection access distinguished from actions. Depends on foundation and naming policy. | `a2ae5877c99`, `d5ecd49a1f3`, `45e01ff6f42` |
 | 5 | 4059 + relevant 4062: Wicket regions/preparation | Planned. Entity-page preparation, region rendering and action prompts; Ajax ancestry, serialization and failure cleanup. The 4062 source touches Wicket as well as core: extract the relevant portions here rather than blindly applying step 4 commits. | `a8264fd1f9b`, relevant parts of `a2ae5877c99` / `45e01ff6f42` |
 | 6 | 4062 + 4067: collection detail and volume | Planned. Table phases, rows, cells, row actions; deliver detail levels, request budgets and collection aggregates with the new detail. Depends on Wicket regions. | `8e5eaab6145`, `036a28a23c2`, `5a1dc2dd17b` |
@@ -130,3 +130,5 @@ Agent bridge completed and archived on 2026-10-07: planning `a7c3d3584e6`, imple
 Step 3 is promoted into [port-current-action-context](../changes/port-current-action-context/proposal.md), with design, delta requirements and implementation tasks. It ports `6d037db6609` while incorporating the invoked/domain-facing identity correction from `f66b8f17a89`. The scope includes current-action queries, checked/skipped/unknown rule status, physical mixin receivers, separate contributed identifiers and nested success/failure restoration; it adds no spans. Public guidance and a [...]
 
 Planning baseline: `41d8db75a06` on CAUSEWAY-4068-v4. Refresh main and select a CAUSEWAY-4058 branch before implementation. CAUSEWAY-4065 application-defined spans remains a later dependent forward port; no implementation or validation is claimed by this proposal.
+
+Current action context implemented on CAUSEWAY-4058 after planning commit `9a250065cfa`. All nine tasks completed; [validation evidence](../changes/port-current-action-context/validation.md) records 29 focused tests and 77 executed interact regressions (2 existing disabled tests), documentation rendering and main-specific mixin identity adaptation. Implementation is recorded in the checkpoint commit containing this update; the change remains active, ready for review/archive.
diff --git a/regressiontests/interact/src/test/java/org/apache/causeway/testdomain/interact/WrapperInteraction_1_IntegTest.java b/regressiontests/interact/src/test/java/org/apache/causeway/testdomain/interact/WrapperInteraction_1_IntegTest.java
index 914f05b9f63..7bc700b1ef8 100644
--- a/regressiontests/interact/src/test/java/org/apache/causeway/testdomain/interact/WrapperInteraction_1_IntegTest.java
+++ b/regressiontests/interact/src/test/java/org/apache/causeway/testdomain/interact/WrapperInteraction_1_IntegTest.java
@@ -23,6 +23,7 @@
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -33,6 +34,10 @@
 import org.apache.causeway.applib.annotation.Action;
 import org.apache.causeway.applib.annotation.DomainObject;
 import org.apache.causeway.applib.annotation.Nature;
+import org.apache.causeway.applib.services.iactn.ActionInvocation;
+import org.apache.causeway.applib.services.iactn.ActionInvocation.RuleChecking;
+import org.apache.causeway.applib.services.wrapper.WrapperFactory;
+import org.apache.causeway.applib.services.wrapper.control.SyncControl;
 import org.apache.causeway.applib.services.wrapper.InvalidException;
 import org.apache.causeway.core.config.presets.CausewayPresets;
 import org.apache.causeway.core.metamodel.facets.all.named.MemberNamedFacet;
@@ -55,6 +60,12 @@
                 WrapperInteraction_1_IntegTest.Customer.class,
                 WrapperInteraction_1_IntegTest.ConcreteMixin.class,
                 WrapperInteraction_1_IntegTest.ConcreteMixin2.class,
+                WrapperInteraction_1_IntegTest.InvocationProbe.class,
+                WrapperInteraction_1_IntegTest.InvocationProbe_outer.class,
+                WrapperInteraction_1_IntegTest.InvocationProbe_inner.class,
+                WrapperInteraction_1_IntegTest.InvocationProbe_skipRules.class,
+                WrapperInteraction_1_IntegTest.InvocationProbe_direct.class,
+                WrapperInteraction_1_IntegTest.InvocationProbe_fails.class,
         }
 )
 @TestPropertySource({
@@ -102,6 +113,117 @@ public String act(final String startTime, final String endTime) {
         }
     }
 
+    @DomainObject(nature = Nature.VIEW_MODEL)
+    static class InvocationProbe {
+        private org.apache.causeway.applib.services.iactn.InteractionService interactionService;
+        private WrapperFactory wrapperFactory;
+        private boolean outerCurrent;
+        private boolean innerCurrent;
+        private boolean outerCurrentDuringInner;
+        private boolean outerRestored;
+        private boolean skipRulesCurrent;
+        private boolean directCurrent;
+        private InvocationProbe_outer outerMixin;
+        private ActionInvocation outerInvocation;
+        private boolean restoredAfterFailure;
+        private boolean propertyHasNoCurrentAction;
+        private boolean restoredAfterProperty;
+        private boolean otherThreadHasNoAction;
+
+        @org.apache.causeway.applib.annotation.Property(editing = org.apache.causeway.applib.annotation.Editing.ENABLED)
+        public String getName() { return "probe"; }
+        public void setName(String name) {
+            propertyHasNoCurrentAction = interactionService.currentActionInvocation().isEmpty();
+        }
+
+        @Action public void regular() {
+            var invocation = interactionService.currentActionInvocation().orElseThrow();
+            outerCurrent = interactionService.isCurrentActionInvocation(this, "regular", RuleChecking.CHECKED);
+            if (!invocation.getLogicalMemberIdentifier().equals(invocation.getDomainFacingLogicalMemberIdentifier())) {
+                throw new AssertionError("regular action identity differs");
+            }
+        }
+    }
+
+    @Action
+    @RequiredArgsConstructor
+    public static class InvocationProbe_outer {
+        private final InvocationProbe mixee;
+
+        public void act() {
+            mixee.outerMixin = this;
+            mixee.outerInvocation = mixee.interactionService.currentActionInvocation().orElseThrow();
+            if (mixee.interactionService.isCurrentActionInvocation(mixee, "act")) {
+                throw new AssertionError("mixee is not the action receiver");
+            }
+            try {
+                mixee.otherThreadHasNoAction = java.util.concurrent.CompletableFuture.supplyAsync(
+                        () -> mixee.interactionService.currentActionInvocation().isEmpty()).get();
+            } catch (Exception ex) { throw new IllegalStateException(ex); }
+            mixee.outerCurrent = mixee.interactionService
+                    .isCurrentActionInvocation(this, "act", RuleChecking.CHECKED);
+            mixee.wrapperFactory.wrapMixin(InvocationProbe_inner.class, mixee).act();
+            mixee.outerRestored = mixee.interactionService
+                    .isCurrentActionInvocation(this, "act", RuleChecking.CHECKED);
+            try {
+                mixee.wrapperFactory.wrapMixin(InvocationProbe_fails.class, mixee).act();
+            } catch (RuntimeException ex) {
+                mixee.restoredAfterFailure = mixee.interactionService.currentActionInvocation()
+                        .orElseThrow() == mixee.outerInvocation;
+            }
+            mixee.wrapperFactory.wrap(mixee).setName("changed");
+            mixee.restoredAfterProperty = mixee.interactionService.currentActionInvocation()
+                    .orElseThrow() == mixee.outerInvocation;
+        }
+    }
+
+    @Action
+    @RequiredArgsConstructor
+    public static class InvocationProbe_inner {
+        private final InvocationProbe mixee;
+
+        public void act() {
+            mixee.innerCurrent = mixee.interactionService
+                    .isCurrentActionInvocation(this, "act", RuleChecking.CHECKED);
+            mixee.outerCurrentDuringInner = mixee.interactionService
+                    .isCurrentActionInvocation(mixee.outerMixin, "act", RuleChecking.CHECKED);
+        }
+    }
+
+    @Action
+    @RequiredArgsConstructor
+    public static class InvocationProbe_fails {
+        private final InvocationProbe mixee;
+        public void act() {
+            if (!mixee.interactionService.isCurrentActionInvocation(this, "act", RuleChecking.CHECKED)) {
+                throw new AssertionError("failing child not current");
+            }
+            throw new IllegalStateException("expected child failure");
+        }
+    }
+
+    @Action
+    @RequiredArgsConstructor
+    public static class InvocationProbe_skipRules {
+        private final InvocationProbe mixee;
+
+        public void act() {
+            mixee.skipRulesCurrent = mixee.interactionService
+                    .isCurrentActionInvocation(this, "act", RuleChecking.SKIPPED);
+        }
+    }
+
+    @Action
+    @RequiredArgsConstructor
+    public static class InvocationProbe_direct {
+        private final InvocationProbe mixee;
+
+        public void act() {
+            mixee.directCurrent = mixee.interactionService
+                    .isCurrentActionInvocation(this, "act");
+        }
+    }
+
     @Inject SpecificationLoader specificationLoader;
 
     @Test
@@ -144,4 +266,80 @@ void mixinActionAccess() {
         assertEquals(3, wrapper.wrapMixin(InteractionDemo_biArgEnabled.class, new InteractionDemo()).act(1, 2));
     }
 
+    @Test
+    void currentActionInvocationTracksNestedWrapperExecution() {
+        var probe = new InvocationProbe();
+        probe.interactionService = interactionService;
+        probe.wrapperFactory = wrapper;
+
+        assertTrue(interactionService.currentActionInvocation().isEmpty());
+
+        wrapper.wrapMixin(InvocationProbe_outer.class, probe).act();
+
+        assertTrue(probe.outerCurrent);
+        assertTrue(probe.innerCurrent);
+        assertFalse(probe.outerCurrentDuringInner);
+        assertTrue(probe.outerRestored);
+        assertTrue(probe.restoredAfterFailure);
+        assertTrue(probe.propertyHasNoCurrentAction);
+        assertTrue(probe.restoredAfterProperty);
+        assertTrue(probe.otherThreadHasNoAction);
+        assertEquals("act", probe.outerInvocation.getLogicalMemberIdentifier().memberLogicalName());
+        var contributedAction = specificationLoader.specForType(InvocationProbe.class).orElseThrow()
+                .streamRuntimeActions(MixedIn.INCLUDED)
+                .filter(action -> action.getFeatureIdentifier().memberLogicalName().equals("outer"))
+                .findFirst().orElseThrow();
+        assertEquals(contributedAction.getFeatureIdentifier(), probe.outerInvocation.getDomainFacingLogicalMemberIdentifier());
+        assertTrue(interactionService.currentActionInvocation().isEmpty());
+    }
+
+    @Test
+    void currentActionInvocationIncludesSkipRulesWrapperExecution() {
+        var probe = new InvocationProbe();
+        probe.interactionService = interactionService;
+
+        wrapper.wrapMixin(
+                InvocationProbe_skipRules.class,
+                probe,
+                SyncControl.defaults().withSkipRules())
+                .act();
+
+        assertTrue(probe.skipRulesCurrent);
+    }
+
+    @Test
+    void currentActionInvocationExcludesDirectJavaCalls() {
+        var probe = new InvocationProbe();
+        probe.interactionService = interactionService;
+
+        new InvocationProbe_direct(probe).act();
+
+        assertFalse(probe.directCurrent);
+    }
+
+    @Test
+    void regularActionHasCheckedStateAndSameDomainIdentity() {
+        var probe = new InvocationProbe();
+        probe.interactionService = interactionService;
+        wrapper.wrap(probe).regular();
+        assertTrue(probe.outerCurrent);
+        assertTrue(interactionService.currentActionInvocation().isEmpty());
+    }
+
+    @Test
+    void topLevelFailureAndLaterInteractionHaveNoStaleAction() {
+        var probe = new InvocationProbe();
+        probe.interactionService = interactionService;
+        assertThrows(RuntimeException.class, () -> wrapper.wrapMixin(InvocationProbe_fails.class, probe).act());
+        assertTrue(interactionService.currentActionInvocation().isEmpty());
+        interactionService.runAnonymous(() -> {
+            assertTrue(interactionService.currentActionInvocation().isEmpty());
+            wrapper.wrap(probe).regular();
+            assertTrue(probe.outerCurrent);
+            assertTrue(interactionService.currentActionInvocation().isEmpty());
+        });
+        assertTrue(interactionService.currentActionInvocation().isEmpty());
+    }
+
+
 }


(causeway) 01/03: CAUSEWAY-4058: propose current action invocation context

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

danhaywood pushed a commit to branch CAUSEWAY-4058
in repository https://gitbox.apache.org/repos/asf/causeway.git

commit 5c43e44b08579fddb2bba83727a78edc0c5b1dbd
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Fri Oct 9 13:23:13 2026 +0100

    CAUSEWAY-4058: propose current action invocation context
---
 .../port-current-action-context/.openspec.yaml     |  2 +
 .../changes/port-current-action-context/design.md  | 49 +++++++++++++++
 .../port-current-action-context/proposal.md        | 28 +++++++++
 .../current-action-invocation-context/spec.md      | 69 ++++++++++++++++++++++
 .../changes/port-current-action-context/tasks.md   | 17 ++++++
 .../planned-changes/otel-forward-port-roadmap.md   |  8 ++-
 6 files changed, 172 insertions(+), 1 deletion(-)

diff --git a/openspec/changes/port-current-action-context/.openspec.yaml b/openspec/changes/port-current-action-context/.openspec.yaml
new file mode 100644
index 00000000000..81264324b05
--- /dev/null
+++ b/openspec/changes/port-current-action-context/.openspec.yaml
@@ -0,0 +1,2 @@
+schema: spec-driven
+created: 2026-10-09
diff --git a/openspec/changes/port-current-action-context/design.md b/openspec/changes/port-current-action-context/design.md
new file mode 100644
index 00000000000..0f8f20c3d17
--- /dev/null
+++ b/openspec/changes/port-current-action-context/design.md
@@ -0,0 +1,49 @@
+## Context
+
+Main exposes `Interaction.getCurrentExecution()` through InteractionProvider but lacks typed current-action queries and aggregate rule status. Main creates ActionInvocation in MemberExecutorServiceDefault and executes it through InteractionCarrier; maintenance's runtime used a different interaction layout. InteractionHead already separates owner (domain object) from target (physical receiver).
+
+The maintenance design is recoverable at `473d2c218c4^:openspec/changes/archive/2026-09-22-expose-current-action-invocation-context/`. Implementation `6d037db6609` supplies the API; `f66b8f17a89` corrects a later conflation of invoked and domain-facing identifiers. Include the corrected contract now without porting ApplicationSpanService.
+
+## Goals / Non-Goals
+
+**Goals:** additive public queries, exact identity checks, explicit checked/skipped/unknown status, physical mixin semantics and nested restoration on success/failure. Preserve invocation, publishing, DTO and observation behavior.
+
+**Non-Goals:** rule evaluation changes; per-rule evaluation provenance; viewer-origin classification; intercepting direct Java calls; new telemetry spans; application span service; semantic naming/rendering forward ports.
+
+## Decisions
+
+### Derive read-only queries from the current execution
+
+Add default methods on InteractionProvider so existing implementations remain compatible. `currentActionInvocation()` returns only the current execution when it is an ActionInvocation; do not search ancestors or fall back to prior executions. Implement exact predicates with target identity (`==`) and Identifier equality. Provide member-name convenience and expected RuleChecking overloads. Null query arguments are rejected consistently with maintenance's nonnull contract. A new thread-loc [...]
+
+### Record aggregate status without changing rule processing
+
+Add RuleChecking CHECKED, SKIPPED, UNKNOWN and `isChecked()`. Preserve the four-argument constructor, delegating to UNKNOWN; add explicit-status and separate-identity overloads. In the mediated executor use USER → CHECKED and FRAMEWORK → SKIPPED, consistent with normal wrapper and withSkipRules behavior. PASS_THROUGH returns before invocation creation and creates no action frame. This aggregate state describes the framework rule-checking mode, not an inventory or guarantee that every pos [...]
+
+### Preserve physical and domain-facing identity separately
+
+Keep `getTarget()` as the actual method receiver and `getLogicalMemberIdentifier()` as the invoked method identifier. A mixin therefore matches `this` and its implementation method (typically act), not the mixed-in object or contributed action name. Add `getDomainFacingLogicalMemberIdentifier()` for the contributed action identity; regular actions and legacy constructors use the invoked identifier for both. Derive the contributed identifier from main's owning action/InteractionHead metad [...]
+
+### Verify existing carrier lifecycle
+
+Nested wrapper calls temporarily expose the child and restore the parent after success or failure. Tests must also show no current action after completion, no ancestor action exposed during a property edit, and no state leakage between interactions/threads. Fix carrier lifecycle only if a focused reproduction shows a main defect; do not replace the interaction carrier with maintenance code.
+
+### Document as invocation context, not new instrumentation
+
+Add public API examples showing a defensive check that is skipped only for the matching action with CHECKED status. Document same-name overload ambiguity, UNKNOWN, physical mixin receivers and direct Java-call limitations. Keep the M3 how-to addition brief: this API supports later application-defined spans and creates no telemetry itself; no artificial manual telemetry scenario is required.
+
+## Risks / Trade-offs
+
+- [Risk] Presence is mistaken for proof of validation → examples explicitly require CHECKED and explain aggregate semantics.
+- [Risk] Same-name overloads collide → full Identifier overload is the exact form.
+- [Risk] Equal domain entities match inadvertently → use identity and test equal-but-distinct receivers without invoking equals.
+- [Risk] A direct recursive Java call can observe an outer framework invocation for the same receiver/method → explain that the API reports the current framework execution, not arbitrary Java stack frames.
+- [Risk] Changing invoked identifiers breaks publishing or DTOs → retain them, expose domain identity separately and verify mixin regressions.
+
+## Migration Plan
+
+Additive interface default methods and constructor overloads require no application configuration or data migration. Use the main release's @since convention (4.x), not maintenance's 2.x annotations. No remote freshness checks have been made during planning; refresh target main and create/select the implementation branch before applying.
+
+## Open Questions
+
+None requiring user input. Confirm main's contributed-action metadata lookup during implementation and record it in validation evidence.
diff --git a/openspec/changes/port-current-action-context/proposal.md b/openspec/changes/port-current-action-context/proposal.md
new file mode 100644
index 00000000000..7d5b9d8e1b6
--- /dev/null
+++ b/openspec/changes/port-current-action-context/proposal.md
@@ -0,0 +1,28 @@
+## Why
+
+Application actions currently have to inspect an untyped interaction execution and reconstruct receiver/identifier checks to know whether Causeway is executing that exact action and has checked its rules. Forward-port CAUSEWAY-4058 so defensive domain code can use a supported query API, and establish reliable invocation identity for later application-defined spans.
+
+## What Changes
+
+- Add `InteractionProvider.currentActionInvocation()` and receiver/identifier or member-name predicates, including rule-status variants.
+- Add aggregate `ActionInvocation.RuleChecking` states CHECKED, SKIPPED and UNKNOWN, preserving the existing constructor with UNKNOWN as its default.
+- Record rule status in main's action executor without changing validation or execution behavior.
+- Preserve physical receiver and invoked identifier semantics for mixins, with a separate domain-facing identifier incorporating the correction from CAUSEWAY-4065.
+- Cover nested success/failure restoration and document direct Java-call limitations.
+- Update public API guidance and the M3 telemetry how-to with a short explanation of this supporting capability.
+
+## Capabilities
+
+### New Capabilities
+
+- `current-action-invocation-context`: Read-only current-action queries, explicit aggregate rule status, nested execution behavior and physical/domain-facing mixin identity.
+
+### Modified Capabilities
+
+None. Existing observation boundaries and export behavior remain unchanged.
+
+## Impact
+
+`api/applib` InteractionProvider, ActionInvocation and Execution Javadoc; `core/runtimeservices` MemberExecutorServiceDefault; focused applib tests and `regressiontests/interact` wrapper tests; public documentation and M3 how-to. No new dependencies, configuration, persisted fields or tracing spans.
+
+Provenance: maintenance `6d037db6609`, corrected identity behavior in `f66b8f17a89`. Adapt to main's interaction carrier instead of copying maintenance's interaction implementation. Planning baseline is `41d8db75a06` on CAUSEWAY-4068-v4; refresh main and select a CAUSEWAY-4058 implementation branch before apply.
diff --git a/openspec/changes/port-current-action-context/specs/current-action-invocation-context/spec.md b/openspec/changes/port-current-action-context/specs/current-action-invocation-context/spec.md
new file mode 100644
index 00000000000..cbf420d97db
--- /dev/null
+++ b/openspec/changes/port-current-action-context/specs/current-action-invocation-context/spec.md
@@ -0,0 +1,69 @@
+## ADDED Requirements
+
+### Requirement: Provider exposes only the current action execution
+
+InteractionProvider SHALL provide a default `currentActionInvocation()` returning the current ActionInvocation when present. It SHALL return empty for no interaction, no current execution or a current non-action execution, without searching prior or ancestor executions. The query SHALL NOT create executions or require observations to be enabled.
+
+#### Scenario: Current action
+- **WHEN** the current execution is an action invocation
+- **THEN** the provider returns that invocation
+
+#### Scenario: No action or current property edit
+- **WHEN** no action execution is current, including a property edit nested within an action
+- **THEN** the accessor returns empty rather than the prior or parent action
+
+### Requirement: Current-action matching uses receiver identity and invoked action identity
+
+InteractionProvider SHALL offer predicates for a nonnull target with a full Identifier or logical member name, and corresponding overloads with expected RuleChecking. Predicates SHALL compare receiver identity without calling equals. Full Identifier matching SHALL include the complete identifier; the name-only form SHALL explicitly document that it does not distinguish same-name overloads. Null query arguments SHALL be rejected.
+
+#### Scenario: Equal but distinct receiver
+- **WHEN** another receiver is equal to the current receiver but is a different Java instance
+- **THEN** current-action matching returns false without invoking receiver equality
+
+#### Scenario: Exact identifier and convenience name
+- **WHEN** the same receiver is tested with the current full identifier or its logical member name
+- **THEN** the predicates match, while a different full identifier does not match even if its member name is the same
+
+#### Scenario: Expected status and invalid query arguments
+- **WHEN** the receiver/action match but the supplied rule status differs, or a required query argument is null
+- **THEN** the status predicate returns false for the mismatch and rejects the null query argument
+
+### Requirement: Action invocations expose aggregate rule-checking state compatibly
+
+ActionInvocation SHALL expose RuleChecking CHECKED, SKIPPED and UNKNOWN with isChecked true only for CHECKED. The existing constructor SHALL remain available and default to UNKNOWN. Runtime USER invocations SHALL report CHECKED and FRAMEWORK invocations SHALL report SKIPPED, without changing rule evaluation. Explicit rule status SHALL be nonnull. PASS_THROUGH SHALL retain its existing behavior without creating a new action invocation.
+
+#### Scenario: Checked and skip-rules wrapper calls
+- **WHEN** a regular wrapper action and a withSkipRules action execute
+- **THEN** their current invocations report CHECKED and SKIPPED respectively, and status-aware predicates distinguish them
+
+#### Scenario: Legacy construction
+- **WHEN** a third-party or reconstructed invocation uses the existing constructor
+- **THEN** its status is UNKNOWN and isChecked is false
+
+#### Scenario: Standalone unmediated call
+- **WHEN** a direct Java or pass-through method call occurs with no existing action execution
+- **THEN** it does not gain a current action invocation or a claim of checked rules
+
+### Requirement: Mixin invoked identity remains separate from domain identity
+
+ActionInvocation SHALL retain the physical method receiver and invoked logical identifier. It SHALL additionally expose a nonnull domain-facing logical identifier. For regular actions and existing constructors both identifiers SHALL coincide; runtime mixin invocations SHALL expose the contributed action identifier separately without changing invoked identity or existing publishing/DTO semantics.
+
+#### Scenario: Mixin action queries itself
+- **WHEN** a mixin action queries using its mixin instance and invoked implementation identifier or member name
+- **THEN** it matches, while the mixed-in domain object does not match as the physical receiver
+
+#### Scenario: Contributed identity
+- **WHEN** a mixin contributes an action with a different domain-facing name
+- **THEN** its invocation retains the mixin implementation identifier and separately exposes the contributed action identifier
+
+### Requirement: Nested action context is restored without stale state
+
+Queries SHALL reflect the existing interaction execution stack. Nested actions SHALL become current during their execution and restore the previous execution after success or failure. Completed invocations SHALL NOT remain current or leak into another interaction/thread. Direct Java calls SHALL NOT create their own frames; documentation SHALL explain that an existing outer framework invocation can remain visible.
+
+#### Scenario: Nested action succeeds or throws
+- **WHEN** an outer action invokes a nested wrapper action that completes or throws
+- **THEN** the child is current only during its execution and the outer invocation is restored when control returns
+
+#### Scenario: Interaction completes
+- **WHEN** a mediated top-level action completes and a later interaction or another thread queries current action
+- **THEN** the completed invocation is not exposed as current
diff --git a/openspec/changes/port-current-action-context/tasks.md b/openspec/changes/port-current-action-context/tasks.md
new file mode 100644
index 00000000000..db53fac6c01
--- /dev/null
+++ b/openspec/changes/port-current-action-context/tasks.md
@@ -0,0 +1,17 @@
+## 1. Public invocation API
+
+- [ ] 1.1 Add InteractionProvider default typed accessor and identity/identifier/member-name predicates, including expected-status variants and documented null/overload semantics.
+- [ ] 1.2 Add RuleChecking, compatible constructors and separate domain-facing identifier to ActionInvocation; correct Execution receiver Javadoc and use main release @since annotations.
+- [ ] 1.3 Add applib tests for absent/non-action execution, identity without equals, full identifier versus name, null arguments, statuses, legacy defaults and regular/mixin identifier separation.
+
+## 2. Main runtime integration
+
+- [ ] 2.1 Verify main USER/FRAMEWORK/PASS_THROUGH rule paths, record CHECKED/SKIPPED in MemberExecutorServiceDefault and derive domain-facing action identity without changing the invoked identifier.
+- [ ] 2.2 Extend wrapper integration regressions for normal/skip-rules actions and mixin physical receiver/contributed identifier behavior, preserving existing publishing/DTO results.
+- [ ] 2.3 Verify nested success/failure and property-edit restoration, cleanup after completion and thread/interaction isolation; make only reproduced lifecycle fixes if necessary.
+
+## 3. Documentation and validation
+
+- [ ] 3.1 Update InteractionProvider/ActionInvocation public guidance with exact checked-action examples, UNKNOWN, overloads and direct Java-call limitations.
+- [ ] 3.2 Add a concise supporting-capability explanation to the M3 otel-howto, making clear that the context API adds no spans and supports the later application-span forward port.
+- [ ] 3.3 Run focused applib/runtime tests and applicable interact regression profile; render changed documentation and record commands, versions, results and remaining limitations in validation.md.
diff --git a/openspec/planned-changes/otel-forward-port-roadmap.md b/openspec/planned-changes/otel-forward-port-roadmap.md
index 5734ebe858d..b07db600818 100644
--- a/openspec/planned-changes/otel-forward-port-roadmap.md
+++ b/openspec/planned-changes/otel-forward-port-roadmap.md
@@ -13,7 +13,7 @@ Main already contains CAUSEWAY-3975, including instrumentation absent from the s
 | 1 | 3975: foundation reconciliation | **Archived:** [completed change](../changes/archive/2026-09-27-reconcile-main-observation-foundation/proposal.md). Lifecycle cleanup, activation/registry selection, Boot/agent compatibility evidence, threshold behavior, preservation of main instrumentation. | `9b8b66d5b7b`, `e6752f882b5`; compare main's original 3975 implementation |
 | 1b | CAUSEWAY-4096: standardize observation names, metadata and duration-filtering policy | **Archived (2026-10-05):** [standardize observation policy](../changes/archive/2026-10-05-standardize-observation-policy/proposal.md) on branch `CAUSEWAY-4096`. Follow-up to 3975: stable operation names vs contextual names; review bookmark/query-description names, identity attributes, hard-coded duration threshold and missing exported parents. Set policy before adding new semantic names. | Main  [...]
 | 2 | CAUSEWAY-4068 (3975/4068 provenance): classification and correlation | **Archived (2026-10-07):** [classify and correlate observations](../changes/archive/2026-10-07-classify-and-correlate-observations/proposal.md). Foreground/background classification, root interaction ID, fixed execution-mode attribute key. Depends on foundation; adapt to main's interaction layers. | `024bd33128d`, `0325232a3c7`, `3083a51eabd` |
-| 3 | 4058: current action context | Planned. Exact-current-action queries and rule-checking state; nested restoration and physical mixin receiver semantics. Can proceed independently of most telemetry work. | `6d037db6609`; account for `f66b8f17a89` |
+| 3 | CAUSEWAY-4058: current action context | **Proposed:** [port current action context](../changes/port-current-action-context/proposal.md). Exact-current-action queries and rule-checking state; nested restoration and physical mixin receiver semantics. Can proceed independently of most telemetry work. | `6d037db6609`; account for `f66b8f17a89` |
 | 4 | 4062: semantic member names | Planned. Bounded operation categories with domain-facing contextual names; mixed-in property/collection access distinguished from actions. Depends on foundation and naming policy. | `a2ae5877c99`, `d5ecd49a1f3`, `45e01ff6f42` |
 | 5 | 4059 + relevant 4062: Wicket regions/preparation | Planned. Entity-page preparation, region rendering and action prompts; Ajax ancestry, serialization and failure cleanup. The 4062 source touches Wicket as well as core: extract the relevant portions here rather than blindly applying step 4 commits. | `a8264fd1f9b`, relevant parts of `a2ae5877c99` / `45e01ff6f42` |
 | 6 | 4062 + 4067: collection detail and volume | Planned. Table phases, rows, cells, row actions; deliver detail levels, request budgets and collection aggregates with the new detail. Depends on Wicket regions. | `8e5eaab6145`, `036a28a23c2`, `5a1dc2dd17b` |
@@ -124,3 +124,9 @@ Removal completed and archived on 2026-10-07. Implementation checkpoint `39faf40
 [provide-agent-observation-bridge](../changes/archive/2026-10-07-provide-agent-observation-bridge/proposal.md) moves the sample-owned bridge and manual exclusions into core configuration. Consumers add the optional BOM-managed OTel bridge dependency, attach the Java agent and activate `observation,agent`; Micrometer metrics remain independent. Exported-trace fixtures exercise the production configuration. This is an adoption simplification on CAUSEWAY-4068, with general duration filterin [...]
 
 Agent bridge completed and archived on 2026-10-07: planning `a7c3d3584e6`, implementation `906a897df37`. All 28 focused/exported-trace tests passed before and after the checkpoint, including Micrometer metrics export in agent mode; Petclinic packaging and documentation rendering passed. The new agent bridge requirements are synced to the main specs.
+
+## Current action context proposal (2026-10-09)
+
+Step 3 is promoted into [port-current-action-context](../changes/port-current-action-context/proposal.md), with design, delta requirements and implementation tasks. It ports `6d037db6609` while incorporating the invoked/domain-facing identity correction from `f66b8f17a89`. The scope includes current-action queries, checked/skipped/unknown rule status, physical mixin receivers, separate contributed identifiers and nested success/failure restoration; it adds no spans. Public guidance and a [...]
+
+Planning baseline: `41d8db75a06` on CAUSEWAY-4068-v4. Refresh main and select a CAUSEWAY-4058 branch before implementation. CAUSEWAY-4065 application-defined spans remains a later dependent forward port; no implementation or validation is claimed by this proposal.