Skip to content

Causeway 4042, 4039#3660

Merged
danhaywood merged 53 commits into
maintenance-branchfrom
CAUSEWAY-4042
Jun 30, 2026
Merged

Causeway 4042, 4039#3660
danhaywood merged 53 commits into
maintenance-branchfrom
CAUSEWAY-4042

Conversation

@danhaywood

Copy link
Copy Markdown
Contributor

No description provided.

danhaywood and others added 23 commits June 30, 2026 06:33
… (StackOverflow fix)

ensureNavigationActionsForMixedInAssociations() was invoked on every
streamDeclaredActions(...) call with no re-entrancy guard. Synthesising a
navigation action forces full introspection of the collection's element type
(filterPropertiesOf -> childSpec.streamAssociations), whose post-processing
re-enters this method; with a cyclic collection graph (A.coll<B>, B.coll<A>,
parent/child trees, mutually-referencing view models) this recursed forever and
blew the stack during metamodel loading.

Route the body through a per-spec _Oneshot (navigationActionAdder), mirroring
mixedInActionAdder / mixedInAssociationAdder, so a spec synthesises its
navigation actions at most once and re-entrant calls short-circuit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Introduces a configurable strategy for synthesising the synthetic navigation
("selector") actions for parented collections / scalar references, selected via

    causeway.extensions.command-log.navigation-action-synthesis = INLINE | POST_PROCESS

INLINE (default): the original behaviour, preserved. Own collections are
synthesised eagerly during introspection (ObjectSpecificationDefault) and
mixed-in collections lazily via ensureNavigationActionsForMixedInAssociations()
(now re-entrancy-guarded by the navigationActionAdder one-shot).

POST_PROCESS: a dedicated MetaModelPostProcessor
(SynthesizeNavigationActionsPostProcessor, registered A0_BEFORE_BUILTIN so the
synthesised actions still receive downstream facet post-processing) synthesises
both own and mixed-in navigation actions once per type. This keeps synthesis out
of the lazy streamDeclaredActions path, so it no longer forces re-entrant
element-type introspection during ordinary action access — the root cause of the
StackOverflow on cyclic collection graphs.

- ObjectSpecificationAbstract: extract shared addNavigationActions(Stream); gate
  inline path behind !isNavigationActionPostProcessing(); add public
  synthesizeNavigationActions() entry point for the post-processor.
- ObjectSpecificationDefault: read strategy from config; skip eager synthesis in
  POST_PROCESS mode; override isNavigationActionPostProcessing().

Default is unchanged (INLINE); existing navigation/recording tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…per-spec (not via isEnabled)

Adds SynthesizeNavigationActionsPostProcessorTest, parameterised over both
NavigationActionSynthesis strategies (INLINE, POST_PROCESS), asserting that each
produces the synthetic navigation action for own, mixed-in, and mutually-cyclic
parented collections.

Also drops the post-processor's isEnabled() override: PostProcessor.init()
snapshots enabledPostProcessors via isEnabled() once, which (a) is fragile w.r.t.
when the pipeline is initialised relative to configuration and (b) prevented the
post-processor from running in the unit-test harness. Gating is now performed
per-type, live from configuration, inside ObjectSpecificationAbstract
.synthesizeNavigationActions() (recording-support + POST_PROCESS), so the
post-processor stays in the pipeline and no-ops cheaply under INLINE.

Note: the unit-test harness (loadSpecification) does not run the post-process
sweep, so the test invokes the post-processor as the production sweep would; the
end-to-end StackOverflow regression on a cyclic graph is left for a full-boot
integration test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…sis; fixes stale-snapshot in INLINE

Adds RecordingNavigation_{inline,postProcess}_IntegTest (regressiontests/domainmodel):
boots the framework with command-log recording-support enabled over a domain whose two
view models reference each other via mixed-in parented collections (a cycle), under each
NavigationActionSynthesis strategy. Reaching the test body at all proves the metamodel was
created without recursing without bound during boot (the original StackOverflow); the
assertions further confirm the navigation actions are synthesized for own and mixed-in
(cyclic) collections.

Booting the cyclic domain surfaced a latent bug in the INLINE path: it sourced candidate
associations from the unmodifiableAssociations _Lazy snapshot, captured *before*
addNavigationActions() triggered mixed-in association materialization. On a cyclic graph the
mixed-in collection was therefore not yet present in the snapshot, so its navigation action
was silently omitted (no crash, thanks to the one-shot guard, but missing). Fixed by sourcing
candidates via streamDeclaredAssociations(MixedIn.INCLUDED), which materializes first and reads
fresh - matching the POST_PROCESS path. Non-cyclic behaviour is unchanged; metamodel unit tests
still pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…SS is now the only path

The inline strategy could still recurse to a StackOverflow during metamodel loading on real
domains (synthesis hangs off the lazy streamDeclaredActions path and forces re-entrant
element-type introspection); the post-processor strategy does not. So drop INLINE entirely and
always synthesize the synthetic navigation ("selector") actions via
SynthesizeNavigationActionsPostProcessor, once per type, during the post-processing phase.

- CausewayConfiguration: remove the navigation-action-synthesis property and NavigationActionSynthesis
  enum. Synthesis is now governed solely by causeway.extensions.command-log.recording-support.
- ObjectSpecificationAbstract: remove ensureNavigationActionsForMixedInAssociations() and its call
  from streamDeclaredActions, and isNavigationActionPostProcessing(); synthesizeNavigationActions()
  is now gated only by recording-support (still one-shot, over streamAssociations(INCLUDED)).
- ObjectSpecificationDefault: remove the eager own-collection synthesis block and the strategy field/
  override; introspectTypeHierarchy just creates regular actions and lets the post-processor add the
  navigation actions.
- SynthesizeNavigationActionsPostProcessor: javadoc updated (sole mechanism, recording-support gated).

Tests:
- ParentedCollectionNavigationActionUtilTest / ScalarReferenceNavigationActionUtilTest: synthesis no
  longer happens during loadSpecification in the unit harness (no post-process sweep), so invoke it
  explicitly via a withNavigationActions(spec) helper after loading.
- SynthesizeNavigationActionsPostProcessorTest: de-parameterized (single behaviour).
- RecordingNavigation integ tests: collapsed the two strategy variants into one
  RecordingNavigation_IntegTest (boots with recording-support enabled over the cyclic domain).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
SynthesizeDomainEventsForMixinPostProcessor no longer mutates the shared mixin FacetedMethod when applying mixee/domain-object domain-event defaults.

Instead, it installs object-type-specific local overlay facets onto the mixed-in member, so each contributed member can shadow the shared default without polluting other mixees.

For actions, it also installs a local ActionInvocationFacetForAction overlay because action execution reads the event holder from the invocation facet.

ObjectActionMixedIn now invokes through that local invocation facet when present, while preserving the existing shared delegation path otherwise.

The same local-overlay approach is applied for action, property, and collection domain-event facets.

Synthetic navigation actions remain domain-event neutral and keep their ids, semantics, invocation, and command publishing behavior unchanged.

Validation: focused core/metamodel tests and RecordingNavigation_IntegTest passed with Java 21; OpenSpec validation passed; mvnd clean install confirmed separately.
…nstead handle replay error and return success
@danhaywood danhaywood merged commit 904ffae into maintenance-branch Jun 30, 2026
1 check failed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant