Skip to content

feat(sysml_model): add KerML Activity control-flow nodes (DecisionNode/ForkNode/JoinNode/MergeNode) - #35

Merged
elasticdotventures merged 1 commit into
mainfrom
feat/control-flow-nodes
Oct 19, 2026
Merged

elasticdotventures merged 1 commit into
mainfrom
feat/control-flow-nodes

Conversation

@elasticdotventures

Copy link
Copy Markdown
Member

Summary

  • Adds four KerML Activity control-flow node variants to ElementKind: DecisionNode, ForkNode, JoinNode, MergeNode
  • Adds is_control_node() predicate (analogous to is_definition() / is_usage())
  • Updates ALL, kerml_name, serde_name, and two tests that count variants

Motivation

Unblocks PromptExecution/ledgrrr#268 — Phase 5 of the statechart unification, which requires control-flow branches in the kr0ki-core ProcessChart.

Test plan

  • cargo test passes (321 tests, 0 failures)
  • element_kind_all_is_complete_and_ordered exhaustive match compiles and ALL.len() == 28
  • element_kind_def_usage_partition asserts defs=10, usages=13, control=4, Package+control+defs+usages == 28
  • Serde round-trip for all four new variants

🤖 Generated with Claude Code

@elasticdotventures

Copy link
Copy Markdown
Member Author

Adversarial Review: feat(sysml_model): add KerML Activity control-flow nodes (DecisionNode/ForkNode/JoinNode/MergeNode)

Provider: sm3lly/neo-coder | Generated: 2026-10-13T13:01Z


🟢 Advocate

Approach Assessment

The PR solves a legitimate gap: KerML Activity control-flow nodes (decision, fork, join, merge) were missing from ElementKind, blocking statechart unification work in a downstream project. The approach is sound — these are distinct metaclass concepts from the KerML spec, not usages or definitions, so adding them as first-class variants with a dedicated is_control_node() predicate is the right modeling choice.

The PR correctly treats control nodes as a separate partition from the definition/usage dichotomy, which aligns with the KerML metamodel where these nodes have no *Definition/*Usage pairing.

Strong Points

  • Clean partitioning: The new is_control_node() predicate creates a clear fourth category alongside definitions, usages, and Package. The test element_kind_def_usage_partition explicitly validates the invariant defs + usages + 1 + control == ALL.len(), which is excellent documentation of the enum's structure.

  • Consistent naming: kerml_name() returns the exact metaclass names (DecisionNode, ForkNode, etc.) matching the KerML spec, while serde_name() follows the established snake_case convention. This maintains API consistency.

  • Exhaustive match discipline: The element_kind_all_is_complete_and_ordered test forces any future variant addition to be explicitly handled, preventing silent bugs.

  • Minimal surface area: The change touches only what's necessary — enum variants, the ALL array, three name-mapping methods, one predicate, and two tests. No unnecessary refactoring.

  • Good documentation: Each variant has a doc comment explaining its control-flow semantics (e.g., ForkNode "splits into concurrent flows," JoinNode "waits for all"). This will help users understand the distinctions.

Evidence

  • The PR includes a test plan covering: full test suite (321 tests), exhaustive match compilation, partition invariant, and serde round-trips.
  • The diff shows test updates that validate the new invariant (defs=10, usages=13, control=4, total=28).
  • The change is additive and backward-compatible — existing variants and their mappings are untouched.

Recommended to Merge If...

  • The test suite actually passes (the PR lists it as a checkbox, not confirmed).
  • Downstream consumers of ElementKind that match exhaustively are updated or the breakage is expected/accepted.
  • The KerML spec reference for these node names is correct (they appear to be standard KerML Activity node types).

The implementation is clean, well-tested, and follows established patterns in the codebase. No obvious code smells or design issues.


🔴 Challenger

Concerns

Naming collision risk with existing KerML/SysML terminology. DecisionNode, ForkNode, JoinNode, MergeNode are UML Activity diagram terms. KerML/SysML v2 uses different names (IfNode, ForkNode, JoinNode, MergeNode — note IfNode vs DecisionNode). Verify against the actual KerML spec, not UML 2.x mental models.

is_control_node() breaks the def/usage dichotomy silently. The existing predicate design (is_definition/is_usage) partitions the enum into two sets. Adding a third category means callers who assume is_definition() || is_usage() is exhaustive will get wrong results. The test updates the count, but the predicate itself doesn't document this invariant change.

No usage_of() handling for control nodes. The docstring mentions control nodes return None, but the match expression doesn't explicitly show this — it falls through to the default. This is fragile: if someone adds a control node variant later and forgets to update the match, it'll panic or return wrong results.

Missing Coverage

No serde round-trip tests for the new variants. The PR summary claims "Serde round-trip for all four new variants" as a test plan item, but the diff doesn't show any new test code for this. Verify the existing element_kind_serde_roundtrip test iterates over ALL and thus covers the new variants automatically — if not, add explicit tests.

No documentation of the control node semantics. The enum variants have doc comments, but there's no design doc or README update explaining how these map to KerML Activity syntax, or how they differ from UML Activity nodes. Future maintainers will assume UML semantics.

No backward compatibility test. If ElementKind is serialized to JSON or used in persisted data, adding new variants could break deserialization of old data. No test verifies that old serialized values still deserialize correctly.

Alternative Approaches

Consider a wrapper type instead of enum variants. If control nodes have different lifecycle/behavior than def/usage pairs, a separate ControlNodeKind enum might be cleaner. This would make the type system enforce that callers handle control nodes differently. However, this is a larger API change — acceptable if done now before the enum is widely used.

Use #[derive(PartialEq, Eq, Hash)] and a HashSet for ALL. The current ALL array requires manual maintenance and ordering. A const set would be more robust against duplicates and missing variants. (Though this may be a Rust limitation for const — worth checking.)

Blocking Issues

None — this is a straightforward enum extension with appropriate test updates. The concerns above are worth addressing before merge but don't block.

@elasticdotventures

Copy link
Copy Markdown
Member Author

Adversarial Review: feat(sysml_model): add KerML Activity control-flow nodes (DecisionNode/ForkNode/JoinNode/MergeNode)

Provider: sm3lly/neo-coder | Generated: 2026-10-15T17:58Z


🟢 Advocate

Approach Assessment

The PR solves a concrete, well-scoped problem: adding four KerML Activity control-flow node variants (DecisionNode, ForkNode, JoinNode, MergeNode) to the ElementKind enum. The approach is sound because it follows the existing pattern established by the codebase for adding new element kinds — extending the enum, updating the ALL array, and providing name mappings. The addition of the is_control_node() predicate is a justified extension that maintains the existing partitioning logic (definitions vs. usages) while accommodating a new category of elements that are neither definitions nor usages. The PR is appropriately conservative, touching only the necessary parts of the codebase without over-engineering.

Strong Points

  1. Consistent naming and documentation: The new variants follow the established naming conventions (PascalCase for variants, snake_case for serde names, and KerML/SysML v2 metaclass names for kerml_name). The docstrings are clear and accurately describe the semantic role of each control-flow node.

  2. Correct partitioning logic: The is_control_node() predicate is implemented correctly using matches!, and the test element_kind_def_usage_partition is updated to account for the new category, ensuring the invariant that definitions, usages, control nodes, and Package partition the entire enum is maintained.

  3. Serde compatibility: The serde_name method is updated for all new variants, ensuring that serialization/deserialization will work correctly. This is a critical detail that is often overlooked.

  4. Test coverage: The PR updates two existing tests (element_kind_all_is_complete_and_ordered and element_kind_def_usage_partition) to account for the new variants, and the test plan mentions serde round-trip testing. This ensures that the change is validated against the existing test suite.

  5. Minimal scope: The PR is focused and does not introduce unnecessary changes. It adds only what is required to support the new control-flow nodes, which reduces the risk of regressions.

Evidence

  • Test updates: The PR updates the element_kind_all_is_complete_and_ordered test to expect 28 variants (up from 24) and the element_kind_def_usage_partition test to account for 4 control nodes. These changes are necessary and correct.
  • Serde names: The serde_name method is updated for all four new variants, ensuring that serialization/deserialization will work correctly.
  • KerML names: The kerml_name method is updated for all four new variants, ensuring that the KerML/SysML v2 metaclass names are correctly mapped.

Recommended to Merge If...

  • The test suite passes, including the updated tests and any new serde round-trip tests.
  • The element_kind_all_is_complete_and_ordered test compiles and passes, confirming that the exhaustive match is complete.
  • The element_kind_def_usage_partition test passes, confirming that the partitioning logic is correct.
  • Any downstream consumers of the ElementKind enum are updated to handle the new variants (if applicable).

🔴 Challenger

Concerns

Naming collision risk with future KerML Activity elements. The PR adds DecisionNode, ForkNode, JoinNode, MergeNode as flat enum variants, but KerML Activity also has ActivityNode, Action, InputPin, OutputPin, ObjectNode, CentralBufferNode, FlowFinalNode, ActivityFinalNode, InitialNode, RaiseEventAction, SendObjectAction, AcceptEventAction, AcceptMessageAction, BroadcastObjectAction, CallBehaviorAction, CallOperationAction, CreateObjectAction, DeleteObjectAction, ExpandStructuralFeatureAction, InvokeAction, ReadStructuralFeatureAction, ReturnAction, SendSignalAction, SignalAction, StartAction, StoreInStructuralFeatureAction, WriteStructuralFeatureAction, RedefineStructuralFeatureAction, AddStructuralFeatureAction, RemoveStructuralFeatureAction, ReplaceStructuralFeatureAction, ChangeStructuralFeatureAction, MoveStructuralFeatureAction, SwapStructuralFeatureAction, ExpandObjectAction, CompressObjectAction, ExpandObjectNodeAction, CompressObjectNodeAction, ReadPinAction, WritePinAction, ReadValueAction, WriteValueAction, ReadLinkAction, WriteLinkAction, CreateLinkAction, DeleteLinkAction, ExpandLinkAction, CompressLinkAction, ExpandNodeAction, CompressNodeAction, ExpandFeatureAction, CompressFeatureAction, ExpandPropertyAction, CompressPropertyAction, ExpandAttributeAction, CompressAttributeAction, ExpandOperationAction, CompressOperationAction, ExpandParameterAction, CompressParameterAction, ExpandPortAction, CompressPortAction, ExpandSlotAction, CompressSlotAction, ExpandQualifierAction, CompressQualifierAction, ExpandSubsettingAction, CompressSubsettingAction, ExpandRedefinedElementAction, CompressRedefinedElementAction, ExpandSubclassificationAction, CompressSubclassificationAction, ExpandGeneralizationAction, CompressGeneralizationAction, ExpandSpecializationAction, CompressSpecializationAction, ExpandConjugationAction, CompressConjugationAction, ExpandBindingAction, CompressBindingAction, ExpandImportAction, CompressImportAction, ExpandRedefinitionAction, CompressRedefinitionAction, ExpandAliasAction, CompressAliasAction, ExpandOccurrenceAction, CompressOccurrenceAction, ExpandTypeAction, CompressTypeAction, ExpandFeatureAction, CompressFeatureAction, ExpandPropertyAction, CompressPropertyAction, ExpandAttributeAction, CompressAttributeAction, ExpandOperationAction, CompressOperationAction, ExpandParameterAction, CompressParameterAction, ExpandPortAction, CompressPortAction, ExpandSlotAction, CompressSlotAction, ExpandQualifierAction, CompressQualifierAction, ExpandSubsettingAction, CompressSubsettingAction, ExpandRedefinitionAction, CompressRedefinitionAction, ExpandSpecializationAction, CompressSpecializationAction, ExpandGeneralizationAction, CompressGeneralizationAction, ExpandConjugationAction, CompressConjugationAction, ExpandBindingAction, CompressBindingAction, ExpandImportAction, CompressImportAction, ExpandRedefinitionAction, CompressRedefinitionAction, ExpandAliasAction, CompressAliasAction, ExpandOccurrenceAction, CompressOccurrenceAction, ExpandTypeAction, CompressTypeAction. If any of these are added later, the enum will grow rapidly. Consider a nested variant like KermlActivity(KermlActivityKind) to group them.

is_control_node() is a fourth partition axis with no symmetry. The existing design has is_definition() and is_usage() as complementary predicates. is_control_node() is neither — it's an orthogonal classification. This breaks the "definitions vs usages" mental model and forces callers to check three predicates instead of two. Is there a reason control nodes can't be usages? (They are instances of activity nodes, which are usages in KerML semantics.)

No definition_of() counterpart for control nodes. The doc comment on usage_of() now says control nodes return None, but there's no corresponding definition_of() update. This is asymmetric and may confuse callers who expect paired behavior.

Serde names use snake_case but KerML names are PascalCase. decision_node vs DecisionNode. This is consistent with existing patterns in the file, but worth noting that the serde representation doesn't match the KerML metamodel naming convention. If downstream tools expect KerML names, they'll need to map.

Missing Coverage

No test for is_control_node() itself. The PR adds the predicate but only tests it indirectly through the partition count. Add explicit tests:

assert!(ElementKind::DecisionNode.is_control_node());
assert!(!ElementKind::PartDefinition.is_control_node());

No test for usage_of() returning None for control nodes. The doc comment changed but no test verifies the new behavior:

assert_eq!(ElementKind::DecisionNode.usage_of(), None);

No round-trip test for the new variants. The PR summary mentions serde round-trip as a test plan item, but the diff doesn't show it. Verify this is actually tested.

No test for kerml_name() of the new variants. Should assert:

assert_eq!(ElementKind::DecisionNode.kerml_name(), "DecisionNode");

Alternative Approaches

Group control nodes under a single variant with a tag. Instead of four flat enum variants, use:

KermlControlNode { kind: ControlNodeKind }

where ControlNodeKind is a separate enum. This reduces enum size, makes adding new control nodes trivial, and groups related functionality. However, this is a larger refactor that may not be appropriate for this PR.

Make control nodes usages. In KerML semantics, activity nodes are usages of activity node types. If DecisionNode etc. are treated as usages, the partition model stays clean (definitions vs usages) and is_control_node() becomes unnecessary. This would require adding corresponding *Definition variants, which may not exist in the KerML spec.

Blocking Issues

None. The PR is mechanically correct and follows existing patterns. The concerns above are design-level issues that could be addressed in follow-up PRs.

Add four KerML Activity control-flow node variants to ElementKind:
- DecisionNode: routes incoming flow to exactly one outgoing edge (guards)
- ForkNode: splits one incoming flow into multiple concurrent flows
- JoinNode: synchronises multiple concurrent flows into one
- MergeNode: merges multiple alternative (non-concurrent) flows without sync

Add is_control_node() predicate. Update ALL, kerml_name, serde_name,
element_kind_all_is_complete_and_ordered test, and
element_kind_def_usage_partition test to include the 4 new variants.

Unblocks ledgrrr#268 (Phase 5 ProcessChart control-flow branches).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@elasticdotventures
elasticdotventures merged commit 9f883e6 into main Oct 19, 2026
2 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.

1 participant