feat(validate): add mutuallyExclusive for static conditional-profile conflict checks #29

Merged
yorunikakeru merged 7 commits from fix/issue-24-inter-conflict-conditional-profiles into main 2026-06-10 16:45:30 +02:00
Owner

Summary

  • Adds mutuallyExclusive :: Text -> Text -> ConfigBuilder () DSL combinator to declare two profiles as mutually exclusive
  • validate now statically checks declared pairs for enable/disable conflicts, reporting InterConflict ProfileKind
  • New UnknownExclusiveProfile Text error when a name in a mutuallyExclusive declaration doesn't match any profile
  • Documents the pre-existing conditional-profile exclusion invariant in interConflict

Test Plan

  • cabal test — 56/56 tests pass including 3 new mutuallyExclusive cases
  • Conflict detection: two conditional profiles enabling/disabling same service with mutuallyExclusiveLeft (InterConflict ProfileKind ...)
  • No false positives: declared-exclusive profiles with non-conflicting actions → Right _
  • Unknown name: mutuallyExclusive "left" "ghost"Left (UnknownExclusiveProfile "ghost")
  • Regression: conditional + unconditional without declaration still passes → Right _

Closes #24

## Summary - Adds `mutuallyExclusive :: Text -> Text -> ConfigBuilder ()` DSL combinator to declare two profiles as mutually exclusive - `validate` now statically checks declared pairs for enable/disable conflicts, reporting `InterConflict ProfileKind` - New `UnknownExclusiveProfile Text` error when a name in a `mutuallyExclusive` declaration doesn't match any profile - Documents the pre-existing conditional-profile exclusion invariant in `interConflict` ## Test Plan - [ ] `cabal test` — 56/56 tests pass including 3 new `mutuallyExclusive` cases - [ ] Conflict detection: two conditional profiles enabling/disabling same service with `mutuallyExclusive` → `Left (InterConflict ProfileKind ...)` - [ ] No false positives: declared-exclusive profiles with non-conflicting actions → `Right _` - [ ] Unknown name: `mutuallyExclusive "left" "ghost"` → `Left (UnknownExclusiveProfile "ghost")` - [ ] Regression: conditional + unconditional without declaration still passes → `Right _` Closes #24
The filter is intentional — scheduler guarantees at-most-one active
conditional profile, so static conflict detection can safely defer
to runtime. Without this note, the filter looks like a bug.

Refs #24
Closes #24
choire: update flake.lock
All checks were successful
CI / linux (pull_request) Successful in 3m0s
b74dd16813
yorunikakeru closed this pull request 2026-06-10 16:34:23 +02:00
yorunikakeru reopened this pull request 2026-06-10 16:36:14 +02:00
Sign in to join this conversation.
No reviewers
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!29
No description provided.