design: interConflict silently skips conditional profiles — static conflict detection is incomplete #24

Closed
opened 2026-06-10 14:47:54 +02:00 by yorunikakeru · 0 comments
Owner

Problem

DSL.Validate.interConflict filters out all conditional profiles before checking for enable/disable conflicts:

| (left, right) <- pairs (filter ((Nothing ==) . upCondition) (ucProfiles config))

This means a conditional profile that enables a service and an unconditional profile that disables the same service pass validation without error — even if the condition is always true at runtime.

Example

configuration $ do
    profile "left"  $ when (processRunning "steam") (enable "foo")  -- conditional
    profile "right" $ disable "foo"                                    -- unconditional
-- validate → Right  (no error)

If steam is always running, the executor receives conflicting instructions for foo on every activation cycle.

Rationale (current design)

The filter is intentional: two profiles that are never simultaneously active cannot conflict at the static level, so deferring to the runtime scheduler is correct for the general case. This is tested and documented via the existing test case.

What is missing

  1. No documentation in interConflict or validate explaining why conditional profiles are excluded. A reader unfamiliar with the design assumption (at-most-one-active) will find the filter surprising.
  2. No mechanism for the caller to opt into stricter validation (e.g. "these two conditions are mutually exclusive — check them anyway").
  3. The scheduler presumably needs to re-check conflicts at activation time, but there is no documented contract or test that it does.

Suggestion

At minimum, add a comment in interConflict explaining the mutual-exclusion assumption. Long-term, consider adding a MutuallyExclusive profile relationship declaration that enables static conflict checks for declared-exclusive conditional profiles.

## Problem `DSL.Validate.interConflict` filters out all conditional profiles before checking for enable/disable conflicts: ```haskell | (left, right) <- pairs (filter ((Nothing ==) . upCondition) (ucProfiles config)) ``` This means a conditional profile that enables a service and an unconditional profile that disables the same service pass validation without error — even if the condition is always true at runtime. ## Example ```haskell configuration $ do profile "left" $ when (processRunning "steam") (enable "foo") -- conditional profile "right" $ disable "foo" -- unconditional -- validate → Right (no error) ``` If steam is always running, the executor receives conflicting instructions for `foo` on every activation cycle. ## Rationale (current design) The filter is intentional: two profiles that are never simultaneously active cannot conflict at the static level, so deferring to the runtime scheduler is correct for the general case. This is tested and documented via the existing test case. ## What is missing 1. **No documentation** in `interConflict` or `validate` explaining *why* conditional profiles are excluded. A reader unfamiliar with the design assumption (at-most-one-active) will find the filter surprising. 2. **No mechanism** for the caller to opt into stricter validation (e.g. "these two conditions are mutually exclusive — check them anyway"). 3. **The scheduler** presumably needs to re-check conflicts at activation time, but there is no documented contract or test that it does. ## Suggestion At minimum, add a comment in `interConflict` explaining the mutual-exclusion assumption. Long-term, consider adding a `MutuallyExclusive` profile relationship declaration that enables static conflict checks for declared-exclusive conditional profiles.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
FrogOS/DSL#24
No description provided.