Schedule constraints: what each one does to the forward and backward pass

A constraint is a date imposed on an activity independently of its logic. That sentence is in every glossary, including ours, and it is not enough to read a file with: two constraints can carry the same date on the same activity and produce entirely different schedules, because what separates them is which pass they touch.

This page is the pass-by-pass reading. It sets out what each type does to the forward pass and to the backward pass, which types can manufacture negative float and on which row it lands, where the published definitions and a real tool's behaviour part company, and what specifications permit.

The external statement of what each type does

The one citable, public statement of constraint effects this project has found is GAO-16-89G, Schedule Assessment Guide, Appendix V, Table 10: Common Names for Date Constraints and Their Primary Effects. GAO sources the table to analysis of NDIA Joint Industry/Government UN/CEFACT XML Working Group and Department of Defense information. It is a US Government work stating that it may be reproduced in its entirety, so it is quoted directly here. See also the GAO schedule assessment page.

Type Table 10's stated primary effect Pass affected
Finish on or after (FNET) "Forward pass: if necessary, sets the early dates of the activity so that the early finish equals the constraint date" Forward only
Finish on or before (FNLT) "Backward pass: if necessary, sets the late dates of the activity so that the late finish equals the constraint date" Backward only
Start on or after (SNET) "Forward pass: … early start equals the constraint date" Forward only
Start on or before (SNLT) "Backward pass: … late start equals the constraint date" Backward only
Mandatory start (MSON) "Always sets both early and late start dates equal to the constraint date" Both
Mandatory finish (MFON) "Always sets both early and late finish dates equal to the constraint date" Both
As soon as possible (ASAP) "The default for forward scheduling; sets the early start date as early as possible" Neither — it is the absence of a constraint
As late as possible (ALAP) "The default for backward scheduling; sets the early finish date as late as possible" A placement applied after both passes

Two words in that table carry most of the weight. "If necessary" means the constraint is a bound: it fires when the logic would otherwise breach it and does nothing when the logic already satisfies it. "Always" means the constraint is a pin: it fires regardless. A constraint that always fires and a constraint that never fires are both wrong, and only one of the two looks wrong.

Hard and soft, and why the reported class is wider than the computational one

DCMA-EA PAM 200.1 §4.5 enumerates the hard constraints as Must-Finish-On, Must-Start-On, Start-No-Later-Than and Finish-No-Later-Than, and names As-Soon-As-Possible, Start-No-Earlier-Than and Finish-No-Earlier-Than as soft constraints that, in its words, "enable the schedule to be logic-driven". It states that hard constraints should not exceed 5 percent of incomplete tasks — the pamphlet's threshold and not this project's; see the 14-point page for the population each metric counts.

It is worth separating two senses of "hard" that get merged. The reporting class is what a schedule-quality checker counts, and it includes the no-later-than types, because a date capping the backward pass distorts float even though it cannot move a single early date. The computational class is the set that actually overrides network logic on the forward pass, and it is strictly smaller: the mandatory types and nothing else. A tool reporting "hard constraints: 40" is answering the first question; a reviewer asking what in this file overrides the logic is asking the second.

What each one does to negative float, and which row it lands on

This is the part that changes who reads as late, and it is entirely mechanical.

A backward-only cap does not move a date. Finish-no-later-than caps the late finish, the forward pass is free to run past it, and the breach appears as negative float on the constrained activity. Real exports show exactly this shape: both late dates pinned on the constraint date, the early dates running past it, and the stored total float negative by the overrun.

A forward-only floor does not create negative float at all. Start-no-earlier-than pushes the early start out and leaves the backward pass alone, so the late dates can sit months later with positive float — or months earlier, in which case P6 reports negative float and still does not move the late dates, because the type is a floor and not a pin.

A mandatory constraint moves the breach onto the predecessors. Read as a sentence: a five-day activity with a mandatory start on a Wednesday, behind a predecessor running to the Friday after it, starts on the Wednesday anyway — early and late — and the predecessor goes negative. That negative float on the predecessor is the warning the planner set the constraint to get.

So the same lateness appears on two different rows depending on a constraint type. A delay analysis reading negative float as "this activity is late" has assumed one of those shapes without checking which it has.

Where a published definition and a real tool part company

Table 10 gives the mandatory-start row the alternative names MSO and Mandatory start, and the mandatory-finish row MFO and Mandatory finish. In P6 those are two different constraints in each case, and only one member of each pair behaves the way the table says.

P6's Start On and Finish On do not pin unconditionally. Across the exports read for this project, unstarted activities carrying a Start On constraint include rows whose stored early start sits whole calendar days later than the constraint date — network logic drove it past, and P6 let it. And Finish On rows include stored late finishes earlier than the constraint date, which is the negative-float shape above. A constraint that pinned unconditionally would produce neither population.

This project treats P6 as the reference where the two disagree, because the alternative is moving hundreds of rows away from the answers a real tool stored in order to match a citation. Two lessons generalise. A named constraint type is not a behaviour: two tools can spell a type the same way and resolve it differently, and the file records only the name. And restrictiveness is observable — whether a type pins or bounds can be read off any export carrying both the constraint date and the stored dates, by asking whether any row sits away from the date. Nobody has to take it on authority.

A constraint date is an instant, and that is not a footnote

P6 stores a constraint as a timestamp, not as a day. Resolving that timestamp into a scheduling boundary is a decision, and getting it wrong moves whole days. Two failures found in this project's own code are worth publishing, because both are silent and neither is exotic.

A start-side stamp resolved with a finish-side rule. A constraint dated one minute past the calendar's day start is, on the finish side, a day that has ended; on the start side it is a day that has begun. Applying the finish rule to start-side constraints moved every such row forward by a day — hundreds of date cells across a handful of files, every deviation bounded at one or two days. That bound is itself the diagnosis: a semantics error would not be bounded.

A two-sided finish constraint on a Monday. Finish On and Mandatory Finish are the only two types that both floor the forward pass and cap the backward pass on the finish end, and the two passes resolve the constraint instant by snapping in opposite directions. On a date whose previous working day is not the previous calendar day — a Monday, or the day after a holiday — those snaps land on different boundaries, and a single activity with no predecessors, no successors and no deadline can come out with its early finish after its own late finish, reporting negative float and no violation. The stamps P6 writes into an XER happen to avoid it; a schedule arriving as P6 XML or MS Project XML with a Must Finish On dated to a Monday reaches it on the first try. It is recorded as a defect, with a failing test committed against it.

The general point for anyone auditing schedule tooling: ask what a reader does with the time of day on a constraint, and whether the two passes are asked the same question. Inside clndr_data is where those boundaries come from.

What the specifications require

Constraints are among the most tightly regulated things in a public-agency scheduling specification, and the documents do not agree on the shape of the rule.

Document The restriction, in shape Where
UFGS Artificial float constraints such as "zero free float" or "zero total float" are prohibited; mandatory constraints that ignore or affect network logic are prohibited; additional constraints go to the Contracting Officer case by case §3.3.8
VDOT Constraints limited to the milestones the Contract specifies; Start On, Finish On, Mandatory Start and Mandatory Finish prohibited outright II.2.A(10), (10)(c)
NJDOT Constraint dates permitted only on Completion and interim completion milestones, unless the Resident Engineer agrees otherwise 153.03.01
NYSDOT Any activity constraint the Engineer in Charge has not authorised is a finding; zero-free-float constraints named expressly as float suppression Item 639
MDOT SHA Constraints used only where the Contract defines the restriction
MnDOT The narrative must state the reason and purpose of each constraint §1803.3C.1
SCDOT The narrative must explain each constraint Schedule Narrative
WisDOT Constraints limited to contractual ones §108.4.4.3

Note the structural tension the NJDOT page sets out, because it is easy to write a check that gets it backwards. One family says a constraint must be present, on named milestones, carrying contract dates. Another says a constraint must not be present anywhere but there. Two specifications ask opposite questions of the same field, and one inventory answers both.

What a constraint inventory tells a reviewer

Cheap to produce from any export, and the thing to ask for before arguing about a date. Per constrained activity: the type, as the file spells it; the date, with its time of day and not narrowed to a day; whether it bound, or the logic was already satisfying it; the float at the constrained activity and at its predecessors, which is where a mandatory pin shows up; and whether the file's own stored dates honour it.

That last line catches a schedule nobody rescheduled after editing. It is a statement about the file rather than about the tool reading it, and anyone holding the file can check it.

What this tool does, and what it refuses

It computes the schedule with each constraint applied to the pass or passes its type governs, and reports every constraint the file carries with its type, date and activity. It checks against the clauses of the specification you name and cites the clause.

It records a violation by name where a constraint could not be honoured, and names what beat it — the data date, where the constraint asked for work in the past, or recorded actual dates, where the activity already happened and history wins. Both are correct outcomes; absorbing them silently would show a planner a satisfied constraint that was in fact ignored.

It refuses to decide whether a constraint is artificial, whether it was authorised, or whether its use was reasonable. Artificiality is intent and authorisation is a contract record; the reviewing authority determines both.

Source: web/pages/schedule-constraints.md. Source commit date: 2026-09-11.

See it in practice

Follow the evidence, from the schedule to the finding.

Explore the sample