Conflict#

A scheduling conflict found while placing tasks. It isn't built by hand: read it from ScheduleResult.Conflicts, which lists every conflict of the run. A conflict is data in the result, not an exception. The run still succeeds and returns dates.

Declaration#

namespace Plen;

public abstract record Conflict
{
    public sealed record ConstraintViolation(
        Id Task, ConstraintType Constraint, Violation Violation, Moment Required, Moment Actual) : Conflict;

    public sealed record CalendarNonWorking(Id Task, Moment Proposed, Moment Snapped) : Conflict;
}

Conflict has a private constructor, so these two nested records are its only variants. Read them with switch pattern matching. Each variant is an immutable positional record with value equality and a generated ToString(), such as ConstraintViolation { Task = demo, Constraint = Snlt, Violation = LatestStart, Required = 2026-03-03 00:00, Actual = 2026-03-05 00:00 }.

Variants#

Variant When it occurs
ConstraintViolation A task's constraint placed it at a date its driving dependency link doesn't allow. The constraint wins, and the task keeps the constraint's date. Reported only when SchedulingConfig.ApplyConstraints is true.
CalendarNonWorking A summary moved as one block, and a descendant task landed on non-working time of its calendar. Reported only when SchedulingConfig.StrictCalendar is true.

ConstraintViolation#

Property Type Description
Task Id The task or summary whose constraint won.
Constraint ConstraintType The constraint that placed the task: the task's own, or one inherited from a summary.
Violation Violation Which bound the link contradicts.
Required Moment The date the constraint gave the task. The task has this date in the result.
Actual Moment The date the driving link would have given the task.

CalendarNonWorking#

Property Type Description
Task Id The descendant task that landed on non-working time.
Proposed Moment The task's new start, or its new end when the project is scheduled from the end. The date is on non-working time.
Snapped Moment The date the task keeps. Currently always equal to Proposed: the task isn't moved to working time.

Violation#

Which bound of a constraint the driving link contradicts, in Conflict.ConstraintViolation.Violation. EarliestStart and LatestStart compare start dates; EarliestEnd and LatestEnd compare end dates.

namespace Plen;

public enum Violation
{
    EarliestStart,
    LatestStart,
    EarliestEnd,
    LatestEnd,
}
Value Description
EarliestStart The link wants an earlier start than the constraint allows.
LatestStart The link wants a later start than the constraint allows.
EarliestEnd The link wants an earlier end than the constraint allows.
LatestEnd The link wants a later end than the constraint allows.

Example#

demo must start no later than Tuesday, but its predecessor build ends on Wednesday. The constraint wins, and the result reports the conflict:

using Plen;

var monday = Moment.FromYmdHm(2026, 3, 2, 0, 0)!.Value;
var tuesday = Moment.FromYmdHm(2026, 3, 3, 0, 0)!.Value;

var data = new ProjectData
{
    Config = new SchedulingConfig { ApplyConstraints = true },
    Tasks =
    [
        new TaskData("build") { StartDate = monday, Duration = 3 },
        new TaskData("demo")
        {
            StartDate = tuesday,
            Duration = 1,
            ConstraintType = ConstraintType.Snlt,
            ConstraintDate = tuesday,
        },
    ],
    Links = [new LinkData { Id = 1, Source = "build", Target = "demo" }],
};

var result = Engine.Schedule(data, new SolveOptions());

foreach (var conflict in result.Conflicts)
{
    var text = conflict switch
    {
        Conflict.ConstraintViolation v =>
            $"{v.Task}: {v.Constraint} {v.Violation}, constraint gives {v.Required}, link wants {v.Actual}",
        Conflict.CalendarNonWorking n =>
            $"{n.Task}: on non-working time at {n.Proposed}",
        _ => throw new InvalidOperationException("unknown conflict"),
    };
    Console.WriteLine(text);
}
// demo: Snlt LatestStart, constraint gives 2026-03-03 00:00, link wants 2026-03-05 00:00

The _ arm only satisfies the compiler's exhaustiveness check; no other variant exists.