Skip to content

Add terminal signal dependency states (UNMATCHED, FAILED) behind a flag - #1

Closed
derek-miller wants to merge 1 commit into
mainfrom
derek_miller/signal-terminal-states
Closed

Add terminal signal dependency states (UNMATCHED, FAILED) behind a flag#1
derek-miller wants to merge 1 commit into
mainfrom
derek_miller/signal-terminal-states

Conversation

@derek-miller

Copy link
Copy Markdown
Owner

Draft for internal review before opening upstream. Targets the fork's main, not Netflix/maestro.

Summary

Signal dependencies only have MATCHED/PENDING/SKIPPED. So a dependency that never matched stays PENDING forever once its owning step goes terminal (it reads as "might still match"), and a dependency that fails to resolve has no way to carry the error. This adds two terminal states to StepDependencyMatchStatus, gated behind a flag:

  • UNMATCHED — the owning step reached a terminal state while the dependency was still pending. Produced by a new SignalHandler.onTermination(...) default hook (called from MaestroTask's termination path); MaestroSignalHandler marks still-pending dependencies UNMATCHED.
  • FAILED — resolving the dependency raised a non-retryable error, carried in a new SignalDependency.details (Details). Set via SignalDependency.markFailed(Details).

Backwards compatibility

Gated by maestro.signal.terminal-states-enabled (default false). With it off, the engine emits only MATCHED/PENDING/SKIPPED and behavior is unchanged; details is @JsonInclude(NON_EMPTY) so it never appears. Deserialization stays strict, so the intended rollout is: deploy fleet-wide with the flag off → confirm all readers/consumers understand the new values → then flip the flag.

Design note I'd like a second opinion on

The broker only ever matches a dependency or leaves it pending, so core never produces FAILED — it's the mechanism for handlers that can detect a non-retryable resolution failure (our internal table-partition/relay handler is the first user). So FAILED + markFailed + details ship as an extension point with no in-core producer (covered by unit tests). Open question: is that acceptable upstream, or would you prefer UNMATCHED-only here?

Testing

  • SignalDependenciesTest: markPendingAsUnmatched flips only PENDING (leaves MATCHED/SKIPPED/FAILED untouched), no-change case, and markFailed with details.
  • MaestroSignalHandlerTest: onTermination marks UNMATCHED when enabled, no-op when disabled.
  • Affected modules compile; spotless clean.

Signal dependencies only had MATCHED/PENDING/SKIPPED, so a dependency that
never matched stayed PENDING forever once its step went terminal, and a
dependency that could not be resolved had no way to surface the error.

Add two terminal states to StepDependencyMatchStatus:
- UNMATCHED: the owning step reached a terminal state while the dependency
  was still pending. Set by SignalHandler.onTermination (a new default no-op
  hook the engine calls from MaestroTask termination); MaestroSignalHandler
  marks still-pending dependencies UNMATCHED.
- FAILED: resolving the dependency raised a non-retryable error, carried in
  the dependency details. Set via SignalDependency.markFailed(Details). The
  broker never fails per dependency, so core never produces FAILED; it is the
  mechanism for handlers that can detect a resolution failure.

Gated by maestro.signal.terminal-states-enabled (default false): with it off,
behavior is unchanged and only MATCHED/PENDING/SKIPPED are ever emitted, so
existing readers are unaffected until a fleet opts in.
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