You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@causeway.apache.org by da...@apache.org on 2026/06/06 08:21:00 UTC
(causeway) 08/39: CAUSEWAY-4012: adds proposal for parented collection selector actions
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch CAUSEWAY-4012
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit f0a4913dac4be0a3b5fb0991cf6e0b3539c4517b
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Fri Jun 5 09:00:34 2026 +0100
CAUSEWAY-4012: adds proposal for parented collection selector actions
---
.../.openspec.yaml | 2 +
.../design.md | 81 ++++++++++++++++++++++
.../proposal.md | 29 ++++++++
.../specs/command-export-yaml-result/spec.md | 19 +++++
.../specs/command-replay-result-mapping/spec.md | 27 ++++++++
.../parented-collection-selector-actions/spec.md | 81 ++++++++++++++++++++++
.../specs/safe-action-command-publishing/spec.md | 22 ++++++
.../tasks.md | 36 ++++++++++
8 files changed, 297 insertions(+)
diff --git a/openspec/changes/synthesize-parented-collection-actions/.openspec.yaml b/openspec/changes/synthesize-parented-collection-actions/.openspec.yaml
new file mode 100644
index 00000000000..c53ef21aaaf
--- /dev/null
+++ b/openspec/changes/synthesize-parented-collection-actions/.openspec.yaml
@@ -0,0 +1,2 @@
+schema: spec-driven
+created: 2026-06-05
diff --git a/openspec/changes/synthesize-parented-collection-actions/design.md b/openspec/changes/synthesize-parented-collection-actions/design.md
new file mode 100644
index 00000000000..1a076a26af3
--- /dev/null
+++ b/openspec/changes/synthesize-parented-collection-actions/design.md
@@ -0,0 +1,81 @@
+## Context
+
+`ObjectSpecificationDefault` builds the metamodel for entity types by creating associations from association faceted methods and actions from action faceted methods during full introspection.
+Parented collections already carry enough metadata to know the parent type, collection id, element type, and element scalar properties, but selecting an element from a rendered collection does not currently pass through `ObjectAction` invocation.
+Command export and replay already know how to record logged safe action invocations, exported returned-object metadata, and replay result mappings.
+The missing piece is an invokable, safe metamodel member that represents parent-to-child collection navigation.
+
+## Goals / Non-Goals
+
+**Goals:**
+
+- Generate synthetic actions for parented collections during metamodel introspection.
+- Make each synthetic action invokable by command recording code using the normal `ObjectAction` path.
+- Use a mandatory parent reference parameter and optional scalar filter parameters to identify a child object within the parented collection.
+- Return a single child object bookmarkable through the existing command result metadata flow.
+- Keep ordinary collection rendering and row-click navigation unchanged.
+
+**Non-Goals:**
+
+- Do not replace collection rendering or require end users to use synthetic actions during normal browsing.
+- Do not add a new command export YAML shape.
+- Do not add a new replay mapping SPI.
+- Do not infer arbitrary business identity for non-scalar associations or plural child matches beyond deterministic selection rules.
+
+## Decisions
+
+### Synthesize actions from collection associations after associations are created
+
+Create synthetic selector actions in `ObjectSpecificationDefault` after `replaceAssociations(createAssociations())` has populated declared associations and before the final action list is installed.
+This keeps collection-derived actions close to the metamodel data that defines them and avoids requiring domain developers to add mixins or explicit finder methods.
+The action stream should concatenate declared actions with synthetic actions derived from eligible parented `OneToManyAssociation`s.
+
+Alternative considered: generate mixin classes or require application-level finder methods.
+That would make command recording possible, but it would push framework replay infrastructure concerns into domain applications and would be inconsistent across apps.
+
+### Represent synthetic selectors as internal safe ObjectActions
+
+Each selector should be an `ObjectAction` with safe semantics and an invocation facet that reads the parented collection from the supplied parent object and returns the matching element.
+The action id should be deterministic and namespaced to avoid colliding with developer actions, for example using a reserved prefix plus the collection id.
+The action should carry marker metadata that lets viewers and metamodel export distinguish synthetic recording actions from user-authored actions.
+
+Alternative considered: bypass `ObjectAction` and inject command log entries directly from collection row clicks.
+That would record navigation but would not give replay a command target, parameters, or the existing result mapping lifecycle.
+
+### Parameterize by parent plus optional scalar child values
+
+The first parameter should be mandatory and typed as the parent specification.
+Additional parameters should be optional and derived from scalar properties of the child element specification that are suitable for value equality filtering.
+Synthetic selectors should ignore child collections and reference properties as automatic parameters to avoid deep traversal, unstable object identity, and ambiguous command DTO parameter mapping.
+
+Alternative considered: use a single child bookmark parameter.
+That would not solve replay when recorded and replayed child identifiers differ, and it would not produce the dotted parent-to-child path required by command result mapping.
+
+### Return one selected child or fail deterministically
+
+Invocation should resolve the parent object, access the collection through the existing association facet, filter by supplied scalar values, and return exactly one child when possible.
+If no child matches or multiple children match, invocation should fail with a clear validation or execution error instead of choosing an arbitrary row.
+This makes exported recordings stable and forces the recorder to supply enough scalar values for replay.
+
+Alternative considered: return a list of matching children.
+Replay result mapping expects object-to-object correspondence for later command targets, so returning a list would lose the single dotted path step.
+
+### Integrate through existing safe action command publishing
+
+Synthetic selector actions should be safe actions and should be command logged only when the existing safe action command publishing configuration enables safe action logging or when an equivalent explicit recording path invokes them.
+Once logged, export and replay should use existing returned-object metadata and result mapping behavior.
+This keeps the new behavior additive and avoids special cases in command export and replay.
+
+Alternative considered: always command-publish synthetic selector actions.
+That would surprise applications that are not recording replay scripts and would bypass the disabled-by-default policy introduced for safe actions.
+
+## Risks / Trade-offs
+
+- Synthetic action enumeration could affect viewers or tooling that assume every action comes from application code.
+ Mitigation: add marker metadata and hide the actions from ordinary UI surfaces unless command recording or metamodel tooling opts in.
+- Scalar filters may not uniquely identify every collection element.
+ Mitigation: require exact single-match results and document that recordings must supply enough scalar values.
+- Creating parameters from every scalar child property can produce large action signatures.
+ Mitigation: start with eligible scalar properties and allow later refinement through annotations or configuration if needed.
+- The metamodel currently builds actions from `FacetedMethod`s, so synthetic actions may need a small internal action/facet construction path.
+ Mitigation: isolate synthetic action creation behind a factory and reuse existing facets where practical.
diff --git a/openspec/changes/synthesize-parented-collection-actions/proposal.md b/openspec/changes/synthesize-parented-collection-actions/proposal.md
new file mode 100644
index 00000000000..9c30d9b7492
--- /dev/null
+++ b/openspec/changes/synthesize-parented-collection-actions/proposal.md
@@ -0,0 +1,29 @@
+## Why
+
+Regression export and replay need a complete dotted path through recorded workflows, including navigation from a parent object into its parented child collections.
+Today selecting a row from a rendered collection is a UI interaction rather than a command, so recordings lose the parent-to-child lookup step that is needed to remap objects during replay.
+
+## What Changes
+
+- Synthesize safe `ObjectAction`s in the metamodel for parented collections so a recorder can navigate from a parent object to a child object through an invokable command.
+- Add one mandatory parameter for the parent object and optional scalar parameters that can identify or narrow the child object within the collection.
+- Return the selected collection element as the action result so existing safe action command logging, export, and replay result mapping can establish the recorded-to-replayed object correspondence.
+- Hide these actions from normal application pages unless explicitly surfaced for command recording or metamodel tooling.
+- Preserve existing collection rendering and direct row selection behavior for ordinary UI navigation.
+
+## Capabilities
+
+### New Capabilities
+- `parented-collection-selector-actions`: Synthetic metamodel actions for parented collection navigation that make parent-to-child object selection command-recordable.
+
+### Modified Capabilities
+- `safe-action-command-publishing`: Synthetic parented collection selector actions are safe actions and participate in the existing opt-in safe action command publishing flow.
+- `command-export-yaml-result`: Exported command YAML can include returned object metadata for logged synthetic selector actions.
+- `command-replay-result-mapping`: Replay result mapping can use logged synthetic selector action results to remap later command targets and reference parameters.
+
+## Impact
+
+- Affects metamodel introspection in `ObjectSpecificationDefault`, where associations and actions are assembled.
+- Adds internal synthetic `ObjectAction` support for collection-derived lookup actions and their parameters.
+- May affect viewers, metamodel export, command export, and replay tests that enumerate or invoke actions.
+- Requires tests for action synthesis, parameter shape, invocation semantics, visibility to command recording, and integration with safe action command publishing/export/replay.
diff --git a/openspec/changes/synthesize-parented-collection-actions/specs/command-export-yaml-result/spec.md b/openspec/changes/synthesize-parented-collection-actions/specs/command-export-yaml-result/spec.md
new file mode 100644
index 00000000000..5cc1719bc3b
--- /dev/null
+++ b/openspec/changes/synthesize-parented-collection-actions/specs/command-export-yaml-result/spec.md
@@ -0,0 +1,19 @@
+## ADDED Requirements
+
+### Requirement: Exported command YAML includes synthetic selector returned object metadata
+The system SHALL export logged synthetic parented collection selector action entries using the existing command export YAML shape.
+When a logged synthetic selector action entry has a non-null returned object bookmark, the exported YAML SHALL include returned object metadata containing the bookmark logical type name and identifier.
+The system SHALL preserve replay compatibility for command streams that contain synthetic selector action entries.
+
+#### Scenario: Export logged synthetic selector action with returned object
+- **GIVEN** safe action command publishing is enabled
+- **AND** a synthetic parented collection selector action command log entry has a returned object bookmark
+- **WHEN** the command is selected for export
+- **THEN** the generated YAML contains the embedded command DTO for the synthetic selector action invocation
+- **AND** the generated YAML contains returned object metadata for the selected child object
+
+#### Scenario: Export command stream containing selector and state-changing commands
+- **GIVEN** a command export selection contains a logged synthetic selector action followed by a state-changing command on the selected child object
+- **WHEN** the commands are exported
+- **THEN** the generated YAML preserves both command entries in replay order
+- **AND** the synthetic selector entry includes its returned object metadata when available
diff --git a/openspec/changes/synthesize-parented-collection-actions/specs/command-replay-result-mapping/spec.md b/openspec/changes/synthesize-parented-collection-actions/specs/command-replay-result-mapping/spec.md
new file mode 100644
index 00000000000..d77de0b334a
--- /dev/null
+++ b/openspec/changes/synthesize-parented-collection-actions/specs/command-replay-result-mapping/spec.md
@@ -0,0 +1,27 @@
+## ADDED Requirements
+
+### Requirement: Replay result mapping includes synthetic selector action results
+The system SHALL notify the command replay mapping SPI after successful replay of a synthetic parented collection selector action when both recorded and actual result bookmarks are available.
+The system SHALL use that mapping to remap later replay command targets and reference parameters in the same way as other logged action results.
+The system MUST NOT notify the mapping SPI when replay fails or when either result bookmark is unavailable.
+
+#### Scenario: Replayed selector action maps recorded child to actual child
+- **GIVEN** an imported command entry records a synthetic selector action result bookmark for a child object
+- **AND** replaying that selector action returns a bookmarkable child object
+- **WHEN** replay of the selector action succeeds
+- **THEN** the system notifies the command replay mapping SPI with the recorded and actual child bookmarks
+
+#### Scenario: Later replay command target is remapped from selector result
+- **GIVEN** a replayed synthetic selector action mapped recorded child bookmark `child:A` to actual child bookmark `child:B`
+- **WHEN** a later replay command targets recorded bookmark `child:A`
+- **THEN** replay invokes the later command on actual bookmark `child:B`
+
+#### Scenario: Later replay reference parameter is remapped from selector result
+- **GIVEN** a replayed synthetic selector action mapped recorded child bookmark `child:A` to actual child bookmark `child:B`
+- **WHEN** a later replay command has a reference parameter recorded as `child:A`
+- **THEN** replay supplies actual bookmark `child:B` for that reference parameter
+
+#### Scenario: Selector replay result mapping is skipped when unavailable
+- **GIVEN** an imported synthetic selector action command entry lacks a recorded returned object bookmark
+- **WHEN** replay of that selector action succeeds
+- **THEN** the system does not notify the command replay mapping SPI for that selector action result
diff --git a/openspec/changes/synthesize-parented-collection-actions/specs/parented-collection-selector-actions/spec.md b/openspec/changes/synthesize-parented-collection-actions/specs/parented-collection-selector-actions/spec.md
new file mode 100644
index 00000000000..52892073713
--- /dev/null
+++ b/openspec/changes/synthesize-parented-collection-actions/specs/parented-collection-selector-actions/spec.md
@@ -0,0 +1,81 @@
+## ADDED Requirements
+
+### Requirement: Framework synthesizes selector actions for parented collections
+The system SHALL synthesize a safe metamodel `ObjectAction` for each eligible parented collection association.
+The synthetic action SHALL represent navigation from the collection owner to one selected collection element.
+The synthetic action SHALL have a deterministic identifier that does not collide with developer-authored actions.
+The synthetic action SHALL be distinguishable from developer-authored actions by framework metadata.
+
+#### Scenario: Synthetic action is available for a parented collection
+- **GIVEN** an entity type has a parented collection of child entities
+- **WHEN** the framework fully introspects the entity type
+- **THEN** the metamodel includes a synthetic safe action for selecting one element from that collection
+- **AND** the synthetic action is associated with the collection owner type
+
+#### Scenario: Synthetic action id is deterministic
+- **GIVEN** the framework introspects the same parented collection in two application runs
+- **WHEN** the synthetic selector action is created in each run
+- **THEN** both actions have the same action identifier
+- **AND** the identifier is reserved so it does not conflict with application action ids
+
+#### Scenario: Ordinary action lists can identify synthetic actions
+- **WHEN** a viewer or metamodel exporter enumerates actions for a type
+- **THEN** it can determine whether a listed action is a synthetic parented collection selector action
+
+### Requirement: Selector actions expose parent and scalar child parameters
+The synthetic selector action SHALL define one mandatory parent object parameter.
+The parent parameter SHALL be typed as the collection owner type.
+The synthetic selector action SHALL define optional parameters for eligible scalar properties of the collection element type.
+The synthetic selector action MUST NOT automatically create selector parameters for child collections or reference properties.
+
+#### Scenario: Parent parameter is mandatory
+- **GIVEN** an entity type `Lease` has a parented collection `items`
+- **WHEN** the framework synthesizes the selector action for `items`
+- **THEN** the action has a mandatory parameter typed as `Lease`
+
+#### Scenario: Scalar child properties become optional filters
+- **GIVEN** `LeaseItem` has scalar properties suitable for equality matching
+- **WHEN** the framework synthesizes the selector action for `Lease.items`
+- **THEN** the action has optional parameters corresponding to those scalar properties
+
+#### Scenario: Non-scalar child members are excluded from automatic filters
+- **GIVEN** `LeaseItem` has a child collection and a reference property
+- **WHEN** the framework synthesizes the selector action for `Lease.items`
+- **THEN** the action does not create automatic parameters for the child collection or reference property
+
+### Requirement: Selector action invocation returns exactly one child object
+The synthetic selector action SHALL access the collection from the supplied parent object.
+The synthetic selector action SHALL filter collection elements using the supplied scalar parameter values.
+The synthetic selector action SHALL return the single matching child object when exactly one child matches.
+The synthetic selector action MUST fail clearly when no child matches or when multiple children match.
+
+#### Scenario: Single child is selected
+- **GIVEN** a `Lease` has several `LeaseItem` children
+- **AND** the selector action parameters identify exactly one `LeaseItem`
+- **WHEN** the synthetic selector action is invoked
+- **THEN** the action returns that `LeaseItem`
+
+#### Scenario: No child matches
+- **GIVEN** a `Lease` has no `LeaseItem` matching the supplied scalar values
+- **WHEN** the synthetic selector action is invoked
+- **THEN** the invocation fails with a clear no-match error
+
+#### Scenario: Multiple children match
+- **GIVEN** a `Lease` has more than one `LeaseItem` matching the supplied scalar values
+- **WHEN** the synthetic selector action is invoked
+- **THEN** the invocation fails with a clear ambiguous-match error
+
+### Requirement: Synthetic selector actions are hidden from ordinary navigation
+The system SHALL preserve existing collection rendering and direct row selection behavior.
+The system SHALL hide synthetic selector actions from ordinary application pages unless a command recording or metamodel tooling surface explicitly includes them.
+
+#### Scenario: Ordinary collection browsing is unchanged
+- **GIVEN** a user views a parent object page containing a collection
+- **WHEN** the viewer renders the collection
+- **THEN** the user can still navigate by selecting a rendered collection row
+- **AND** the synthetic selector action is not shown as an ordinary domain action by default
+
+#### Scenario: Command recording can include synthetic actions
+- **GIVEN** a command recording surface supports synthetic selector actions
+- **WHEN** the recorder enumerates actions for navigating a parented collection
+- **THEN** the synthetic selector action is available for recording the parent-to-child navigation step
diff --git a/openspec/changes/synthesize-parented-collection-actions/specs/safe-action-command-publishing/spec.md b/openspec/changes/synthesize-parented-collection-actions/specs/safe-action-command-publishing/spec.md
new file mode 100644
index 00000000000..fd822ed0d7e
--- /dev/null
+++ b/openspec/changes/synthesize-parented-collection-actions/specs/safe-action-command-publishing/spec.md
@@ -0,0 +1,22 @@
+## ADDED Requirements
+
+### Requirement: Synthetic selector actions participate in safe action command publishing
+The system SHALL treat synthetic parented collection selector actions as safe actions for command publishing purposes.
+When safe action command publishing is disabled, invoking a synthetic selector action MUST NOT create a command log entry solely because it is synthetic.
+When safe action command publishing is enabled and a synthetic selector action is invoked through the normal action invocation flow, the system SHALL create a command log entry through the normal command publishing flow.
+
+#### Scenario: Disabled safe action publishing does not log selector action
+- **GIVEN** safe action command publishing is disabled
+- **WHEN** a synthetic parented collection selector action is invoked
+- **THEN** the system does not create a command log entry solely for that invocation
+
+#### Scenario: Enabled safe action publishing logs selector action
+- **GIVEN** safe action command publishing is enabled
+- **WHEN** a synthetic parented collection selector action is invoked
+- **THEN** the system creates a command log entry for the selector action invocation through the normal command publishing flow
+
+#### Scenario: Selector action result bookmark is captured when available
+- **GIVEN** safe action command publishing is enabled
+- **AND** a synthetic selector action returns a bookmarkable child object
+- **WHEN** the invocation is command logged
+- **THEN** the command log entry includes the returned object bookmark when the normal result handling can capture it
diff --git a/openspec/changes/synthesize-parented-collection-actions/tasks.md b/openspec/changes/synthesize-parented-collection-actions/tasks.md
new file mode 100644
index 00000000000..951daaff43e
--- /dev/null
+++ b/openspec/changes/synthesize-parented-collection-actions/tasks.md
@@ -0,0 +1,36 @@
+## 1. Metamodel Synthesis
+
+- [ ] 1.1 Identify eligible parented collection associations during `ObjectSpecificationDefault` introspection after associations are created.
+- [ ] 1.2 Add a factory or helper that creates deterministic synthetic selector action identifiers and marker metadata for eligible collections.
+- [ ] 1.3 Implement synthetic `ObjectAction` construction with safe semantics and integration with existing action enumeration.
+- [ ] 1.4 Ensure synthetic selector actions are hidden from ordinary UI action surfaces unless explicitly requested by recording or metamodel tooling.
+
+## 2. Parameter Model
+
+- [ ] 2.1 Add a mandatory parent object parameter typed as the collection owner specification.
+- [ ] 2.2 Derive optional selector parameters from eligible scalar properties of the collection element specification.
+- [ ] 2.3 Exclude child collections and reference properties from automatic selector parameters.
+- [ ] 2.4 Ensure synthetic parameter metadata is serializable into command DTOs through the normal action invocation path.
+
+## 3. Invocation Semantics
+
+- [ ] 3.1 Implement selector invocation by resolving the supplied parent object and reading the parented collection through the existing association facet.
+- [ ] 3.2 Filter collection elements using supplied scalar parameter values with deterministic equality semantics.
+- [ ] 3.3 Return the single matching child object when exactly one element matches.
+- [ ] 3.4 Fail clearly when no elements match or multiple elements match.
+
+## 4. Command Logging, Export, and Replay Integration
+
+- [ ] 4.1 Verify synthetic selector actions participate in existing safe action command publishing only when that publishing path is enabled.
+- [ ] 4.2 Verify logged selector action results capture returned object bookmarks through existing result handling.
+- [ ] 4.3 Verify exported YAML includes selector command DTOs and returned object metadata without introducing a new YAML shape.
+- [ ] 4.4 Verify replay result mapping uses selector action results to remap later command targets and reference parameters.
+
+## 5. Tests and Validation
+
+- [ ] 5.1 Add metamodel tests for synthetic selector action creation, deterministic ids, marker metadata, and action hiding behavior.
+- [ ] 5.2 Add parameter tests for mandatory parent parameters, optional scalar child parameters, and excluded non-scalar members.
+- [ ] 5.3 Add invocation tests for single match, no match, and ambiguous match outcomes.
+- [ ] 5.4 Add command log export and replay integration tests covering a parent-child-grandchild dotted path such as `Lease -> LeaseItem -> LeaseTerm`.
+- [ ] 5.5 Run focused Maven tests for the metamodel and command log modules touched by the implementation.
+- [ ] 5.6 Run `openspec validate synthesize-parented-collection-actions --strict` and fix any proposal/spec/task validation issues.