Skip to content

Fixing statuses not being checked against lifecycle - #6843

Merged
delchev merged 16 commits into
eclipse-dirigible:masterfrom
TIVMOF:fix/missing-generate
Aug 20, 2026
Merged

Fixing statuses not being checked against lifecycle#6843
delchev merged 16 commits into
eclipse-dirigible:masterfrom
TIVMOF:fix/missing-generate

Conversation

@TIVMOF

@TIVMOF TIVMOF commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

This PR:

Two status writes were never checked against lifecycle:, and the worst one runs last

When an entity declares lifecycle:, the parser validates transitions: entries and workflow
setRelationField / checks: status writes against the edge graph. Two constructs that also write
the lifecycle FK were never added to that check:

  • generates[].sourceStatusIntentParser.java:5276 only verifies the from entity has an
    EntityStatus relation. No edge check.
  • resolves: outcome setStatusvalidateResolveOutcomes only verifies a positive seed id and
    that the record declares a status relation. No edge check.

Both parse and generate cleanly and fail only at runtime, where enforceLifecycleMove throws
ValidationException.

The ordering half makes sourceStatus worse

In Generate.java.template the write order was: target saved → items saved → then the
sourceStatus flip. No try/catch, no rollback. A lifecycle-rejected flip therefore left a committed
target document behind whose source never transitioned, so nothing published -transitioned and
every downstream posting/integration keyed on the new status silently never ran.

Worse, it was unrepairable: the at-most-once guard returns the existing target before reaching
the flip, so a redelivery handed back the orphaned document instead of completing the transition.
Operator symptom: "the invoice exists but the timesheet still shows as approved."

The fix

ParservalidateStatusWritesAgainstLifecycle now covers generates[].sourceStatus and every
resolves: outcome setStatus alongside the workflow setters and checks it already covered. Both
values are static in the model, so it is the same reachability check applied to two more call sites.
A cross-model source is skipped, exactly as the generates block's own checks skip it.

Ordering — the flip moves to just before save(target), after the target is built in memory (so
field assignments still read the pre-flip snapshot). The -transitioned publish stays last: the
transition is complete only once the document it was about exists.

This composes with the redelivery that landed in #6830. A rejected move now throws with nothing
created, and a redelivery after a transient failure re-runs the flip as a no-op
(enforceLifecycleMove short-circuits when previous.equals(next)) and goes on to create what is
missing.

Reviewer notes

  • Behaviour change, not purely a fix. If creating the target has a side effect that itself moves
    the source's status (a chained rollup or posting), the two orderings require different edges —
    old needed post-side-effect → Z, new needs pre-side-effect → Z. Narrow, but real. An author
    relying on it now gets a parse error instead of silence.
  • sourceStatus takes a seed id only. It is not one of the sites StatusSymbolResolver
    rewrites, so a seeded name never reaches the graph. Documented in the module guide; widening it is
    separate work.
  • Out of scope, worth its own issue: the at-most-once guard is
    findAll(eq(backRefProperty, sourceId)) with no further discrimination, so a voided target still
    claims its source and an event-driven create-from can never mint a replacement. postings: solved
    the same problem with a stornoProperty / stornoFilterProperty link; generates has no
    equivalent. Button-driven create-from is unaffected — it carries no guard by design.

Verification

  • LifecycleGraphIntentTest — four new cases (reject + accept for each construct), red-first
    verified: 2 failures without the parser change.
  • IntentEngineIT.generates_completion_hook_flips_the_source_via_targeted_update — extended with
    the ordering assertion (flip < save < publish).
  • engine-intent 695/695, formatter:validate clean.
  • Template rendered through real Velocity; sourceRepository verified in scope at the later publish.

Not run: the IT assertion, for the offline engine-camel reason above.

TIVMOF and others added 16 commits August 10, 2026 08:20
… flip the source before creating the target

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The completion hook now flips the source status BEFORE the target is saved, and
the comment block explaining why moved up with it. That comment names
"-transitioned", so indexOf("-transitioned") found the comment rather than the
sendToTopic call and reported the publish as happening before the save - the
assertion failed on a generator that was doing exactly what it should.

Match the call's argument instead. The sibling assertion just below already
guards against the same trap for updateWithoutEvent; this extends it to the
publish, so an explanatory comment can name the topic without standing in for it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The previous push updated this PR's head but GitHub created no workflow run for
it, leaving the PR displaying the previous head's conclusion. Empty commit to
fire pull_request.synchronize so the checks run against the current code.
…ing-generate

# Conflicts:
#	components/engine/engine-intent/CLAUDE.md
@delchev
delchev merged commit e40fa0f into eclipse-dirigible:master Aug 20, 2026
10 checks passed
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.

Lifecycle edge validation misses generates[].sourceStatus and resolves: setStatus - and Generate flips the source only after the target committed

2 participants