Conformance — the reviewer's guide to the rule packs

A schedule submitted under a specification is checked against that specification by a person, by hand, clause by clause, in an afternoon. The check is slow, inconsistent between reviewers, and undocumented in its reasoning. That is the job these rule packs automate.

A passing report does not mean a compliant schedule. §4 of this document is the list of what the packs do not check, by clause number, and it is the section to read before you rely on a green tick.


1. The packs

Pack Specification Rules Deterministic Hybrid Judgment
ufgs UFGS 01 32 01.00 10 48 18 23 7
asce67 ANSI/ASCE/CI 67-17 35 3 25 7
aace29 AACE RP 29R-03 37 13 20 4
scl SCL Delay and Disruption Protocol, 2nd ed. 35 6 27 2
aace_review AACE RP 78R-13 and 53R-06 61 42 16 3
vdot VDOT progress-schedule special provisions 26 18 6 2
gao GAO Schedule Assessment Guide (GAO-16-89G) 29 15 7 7
aace84 AACE RP 84R-13 8 3 4 1
caltrans Caltrans 2025 Std Specs §8-1.02C 11 4 7 0
paseg NDIA IPMD PASEG (v4.0) 5 3 2 0
nysdot NYSDOT Item 639.2X010011 Type 2, with the NYSDOT CPM Best Practices manual 16 12 4 0
navfac UFGS 01 32 17.00 20 (NAVFAC) 19 10 8 1
wisdot WisDOT SE Region CPM review guidance (Dec 2023) 5 3 2 0
wvdoh WVDOH 2023 Std Specs §108.3, with the WVDOH CPM Schedule Review Manual 16 12 4 0

351 rules across fourteen packs. Read off cli._AUTHORITY_PACKS, first on 2 September 2026 at 331, again on 5 September at 335, when navfac gained the four clauses of UFGS 01 32 17.00 20 outside its §1.10 tree that engine/quality/DUTIES-OUTSIDE-THE-METHOD.md audits, and on 6 September at 351, when wvdoh landed West Virginia's §108.3 with the agency's own reviewer checklist beside it. That is the count of the library; no single command runs all of them, and the two populations have been confused on this page before. §2 gives the count each --jurisdiction actually runs, which is the number a reader should quote about a report.

Until 2 September 2026 the paragraph below said "three" and then named two, when aace84 was already in the table and in no jurisdiction's precedence. The count is now read off Authority: an authority in no governing() precedence is reachable only by --review or --contract.

Every one of the fourteen is selectable by some command. Ten do not appear in any jurisdiction's default count below. aace_review is elected and runs only under --review. The other nine — vdot, gao, aace84, caltrans, paseg, nysdot, navfac, wisdot and wvdoh — are contract-named authorities: they bind when the contract names them, and they are named with --contract:

wvdoh is the second pair after nysdot: §108.3 of West Virginia's standard specification book is a contract term, and the Construction CPM Schedule Review Manual read beside it is training material for the agency's own reviewers and is not. Ten of its sixteen rules cite the specification and may FAIL; the six that cite a numbered checklist item return an OBSERVATION at worst, and a test reads the module source to hold that ceiling rather than trusting a fixture to trip it.

wisdot stretches that description and the stretch is recorded rather than smoothed over. WisDOT SE Region's Sample Schedule Review Comments is not a contract term — it states no requirement in its own voice, and Authority.is_contract_term is False for it alone among the state DOT authorities here. --contract wisdot_se_review_2023 therefore elects it the way --review elects the AACE checklists, and every rule in that pack returns an OBSERVATION rather than a FAIL. docs/WISDOT-PARTITION.md records what the document is evidence of and what it is not.

construct check engine/oracle/cases/update-chain-02-update.xer --jurisdiction us_state_or_private \
    --contract vdot_progress_schedule

which takes that job's count to 98 (26 + 37 + 35). A named document is put ahead of the jurisdiction's own order, not appended to it and not listed as elected — the special provision deletes and replaces §108.03 of Virginia's specifications, so it is a term of the contract, and a contract term outranks a standard for the same reason UFGS §3.8.4 makes ASCE govern over AACE on federal work. --contract is repeatable and takes the documents in the contract's own order, because that ordering is itself a term.

Until 2 September 2026 this paragraph was true of the library and false of every command a user could type: vdot was in no jurisdiction's precedence and no flag reached it, so the 98 was a real number produced by a call only a library caller could make. engine/quality/UNREACHED.md §3.1 is the record; engine/tests/test_unreached.py now asserts the 98 of the command above.

The gao pack takes the same route, and for a different reason. GAO-16-89G is not a contract term by its own force and not an ANSI consensus standard: it is one audit agency's published criteria, and it governs by adoption. So it is in no jurisdiction's default precedence — putting it in us_federal would assert that every federal construction contract incorporates it — and --contract gao_16_89g is the assertion that this contract, solicitation or agency policy is one that does.

construct check engine/oracle/cases/update-chain-02-update.xer --jurisdiction us_federal --contract gao_16_89g

which takes that job's count to 149 (29 + 48 + 35 + 37).

The GAO pack's honest partition is its main product, and it is smaller than 29 rules suggests. Three of the Guide's ten best practices carry no rules at all. Best Practice 3 (assigning resources) and Best Practice 8 (conducting a schedule risk analysis) are refused whole — a Snapshot carries no resource assignments and no three-point duration fields, and every measure Table 11 lists for those two is a count over a field this engine does not read. Best Practice 5 (traceability) carries one rule that refers rather than measures: its horizontal half is the delay-injection probe already implemented once as DCMA check 12 in cpmcore.health, and its vertical half needs schedules at more than one level. engine/src/forensic/rules/gao.py's module docstring is the practice-by-practice partition and the list of what is deliberately not implemented.

Nothing in the GAO pack carries a threshold the Guide does not state, and the Guide says why, at the head of appendix VI: "No 'pass-or-fail' thresholds or tripwires are associated with the measures." Seven of its rules therefore report a measure and refer the question — the float distribution, the duration profile, the critical share, path convergence, the detail-to-milestone ratio, baseline variance, traceability — and none of them can return FAIL, which engine/tests/test_rules_gao.py asserts across the whole pack.

navfac is the first --contract pack that is federal, and the reason it takes that route is the reason it exists. UFGS 01 32 17.00 20 is a Division 01 specification section prepared by NAVFAC, exactly as UFGS 01 32 01.00 10 is one prepared by USACE, and both are terms of the contracts that incorporate them. But a federal construction contract carries one of the two, not both: putting this section into us_federal's default precedence alongside the other would run two specifications against one schedule that contradict each other in plain text, and print both under "Governing documents" on every federal job.

construct check engine/oracle/cases/update-chain-02-update.xer --jurisdiction us_federal --contract ufgs_01_32_17

The contradiction is the pack's argument and not an inconvenience. On out-of-sequence progress, NAVFAC §1.4.2.f says "Out of Sequence progress is not allowed" and states no approval route; USACE §3.3.13 allows it "only on a case-by-case basis subject to approval by the Contracting Officer". One check, one schedule, two verdicts — NAVFAC-1.4.2.f is DETERMINISTIC and UFGS-044 is HYBRID — and which is right is a fact about which section the contract carries. On the share of a baseline that may sit on the longest path, NAVFAC caps it at a bracketed "[30 percent][_____]" whose editing note instructs the specifier to retain 20 and permits up to 80 on linear work; Caltrans §8-1.02C(6) permits 50 percent of a larger population; USACE states no such cap at all. Three authorities, one metric, three answers. Both disagreements are registered in forensic.jurisdiction as Conflict records rather than resolved, because neither federal agency's section is senior to the other's.

That bracket is also why NAVFAC-1.10.1.3 abstains rather than measuring: [30 percent][_____] is a fill-in-the-blank for the specifier, so the threshold is a term the executed contract states and the rule returns NOT_EVALUATED naming longest_path_share_limit until somebody supplies it. It is the clearest case in the library of GAO's "No 'pass-or-fail' thresholds or tripwires are associated with the measures" being true of a specification and not only of an audit guide.

Cost loading is this section's title and no rule in the pack checks it. §1.10.1.10 states arithmetic requirements — the sum of activity costs must equal the contract value, a 40 percent demobilization floor, a bracketed 10 percent commissioning floor, zero cost on Government-responsibility activities — and cpmcore does read costs from TASKRSRC. They stop at the exchange model: forensic.rules.Snapshot carries cpmcore.network.Task, which has no cost field, and cli._load_snapshot does not carry source.assignments past the boundary. A rule reading an absent field passes every schedule, so none was written. corpus/standards/UFGS-01-32-17.00-20_PROVENANCE.json records it as the open item with what becomes decidable when Snapshot carries assignments.

The caltrans pack is the opposite shape, and its numbers are all Caltrans's. VDOT's special provision states triggers — things P6's own Schedule Log reports. §8-1.02C states bands: 50–500 activities, 1–20 working days per activity, and a cap of 50 percent of baseline activities critical or near critical. Two of those fail from either end and the third from one, because the specification states one end. The near-critical threshold the cap needs — total float of not more than 10 working days — is Caltrans's own, and it is in §1-1.07B, the Glossary, not in §8-1.02C; a reviewer looking only at §8 will not find it. §8-1.02C(1) makes the section apply where the Bid Item List carries the level 2 CPM bid item, so it is a contract term and outranks a standard it conflicts with. engine/src/forensic/rules/caltrans.py records the one conflict that follows: AACE 92R-17's near-critical rules give roughly ten working days on a monthly-updated job and two or three on a weekly one, so they nearly agree on the first and do not on the second.

The pack cites clause (13) of the Category III special provision throughout, and gives the Category II and Design-Build equivalents per rule — Category II has no Cost Loading and no Resource Loading clause, so from (11) onwards its numbering runs two lower, and the Design-Build exhibit uses roman numerals. Citing "clause (13)" on a Category II job cites Progress As-built Information (corpus/specifications/README.md §2).

Severity distribution in the UFGS pack: 1 critical, 26 major, 13 minor, 8 info. The one CRITICAL rule is UFGS-043 (§3.3.12, actual dates against the QC daily reports), because §3.3.12 makes that breach automatic disapproval of the whole update — the specification says so; it is not the reviewer's discretion.

The packs check different subjects

The UFGS pack checks a schedule. The ASCE pack mostly checks an analyst.

ASCE 67-17 is a principles standard for how a schedule delay analysis is conducted, ordered, evidenced and bounded. It states no method taxonomy of its own and prescribes no CPM calculation beyond the ones any scheduler already performs. So the subject of most of its 35 guidelines is not the network — it is the conduct of the analysis over the network: whether the critical path was recomputed per delay, whether prior entitlement was resolved before the delay at issue, whether an after-the-fact edit touched the contractor's own preferential sequencing, whether a compensability conclusion cites a remedy-granting clause.

That is why only three ASCE rules are DETERMINISTIC:

The rest need either something the contract said or something the analyst did, and neither is in the schedule file. Marking them DETERMINISTIC to raise a coverage percentage would be worse here than usual: ASCE 67-17 governs on US federal work by UFGS §3.8.4, and a false green tick there is a false green tick in front of a board.


2. Which pack runs depends on the jurisdiction

Only rules from an authority that governs appear in the report — not as a pass, not as a skip. A rule from a document that does not bind would imply the document was considered and satisfied.

--jurisdiction Precedence Rules that run
us_federal UFGS → ASCE 67-17 → AACE 29R-03 120 (48 UFGS + 35 ASCE + 37 AACE 29R-03)
us_state_or_private AACE 29R-03 → ASCE 67-17 72 (37 AACE 29R-03 + 35 ASCE)
neutral AACE 29R-03 → ASCE 67-17 → SCL 107 (37 + 35 + 35)
uk_scl SCL → AACE 29R-03 72 (35 SCL + 37 AACE 29R-03)

Add 61 to any row where the analyst passes --review, which elects the aace_review pack. Then add, per --contract name given: 26 vdot_progress_schedule, 29 gao_16_89g, 8 aace_84r_13, 11 caltrans_8_1_02c, 5 ndia_paseg. Any --contract name also moves its document to the head of that row's precedence, ahead of what the jurisdiction infers, because incorporation by name is itself a term.

ndia_paseg is the exception that proves what the flag means, and it is recorded here rather than left to surprise a reader of §2's arithmetic: PASEG states no requirement — shall appears 0 times in every edition this project holds — so its five rules return only OBSERVATION or NOT_EVALUATED. They add 5 to the rules that run and 0 to both sides of the score, which lowers a run's reported coverage. Naming a guide with --contract does not make it bind; it makes it visible. engine/src/forensic/rules/paseg.py states the measurement and its editions.

These are the counts a command runs. The ~~295~~ 335 in §1 is the library, and no --jurisdiction produces it. The distinction has been got wrong twice on this page, in both directions — and a third time, in a way neither of those was: this sentence went on naming 295 while §1's own pack table was corrected to 311, then 331, then 335. The owner of the count disagreed with itself for as long as the count kept moving, which is exactly when a reader is most likely to come here for it. Corrected 6 September 2026, re-derived from the registries.

AACE 29R-03 is also implemented as the taxonomy (forensic.mip), the Source Validation Protocols (forensic.svp), the prospective TIA procedure (forensic.tia) and the concurrency doctrines (forensic.concurrency). The aace29 pack is the clause-by-clause conformance layer over the same document and does not replace them.

This table previously read 83 / 35 / 35 / 0 and said no AACE 29R-03 or SCL pack existed. Both packs existed, registered and tested; they were absent from cli.py's _AUTHORITY_PACKS, so a uk_scl run resolved both of its governing documents to nothing and printed an empty findings list. The map is now total over Authority and refuses at import if it is not.

Rules are ordered by precedence, so the report reads top-down in the order the documents bind — the contract's own terms first, then the standard it names.


3. What a rule is allowed to do when it lacks an input

Three things, and never a fourth.

  1. If the schedule alone settles the clause, decide it.
  2. If the clause needs a number or a record the schedule does not contain, report NOT_EVALUATED and name the term. No industry default is ever substituted.
  3. If the clause is not a computation at all — "lags must be reasonable" — the rule assembles the evidence, states the question, and returns REFERRED.

See READING-A-REPORT.md §3 for what each of those obliges you to do.

Interpretations the UFGS pack takes

Where a clause admits more than one reading, the reading taken is named here rather than buried, so that a reviewer who disagrees can find it.

Interpretations the ASCE pack takes

Citations use the guideline numbers the standard carries (chapter and guideline, e.g. §8.3). ASCE 67-17 does not number the commentary beneath its guidelines, so a rule resting on commentary cites the guideline it sits under and says so in the summary.

Where ASCE and AACE give different answers

Three ASCE guidelines produce answers AACE would not, and on federal work ASCE's answer binds:


4. What the packs do NOT check

This is the section that stops a passing report from meaning more than it does.

UFGS 01 32 01.00 10 — clauses deliberately not implemented

The pack was written against the 103-item clause inventory in corpus/analysis/RULES.md. Forty-eight are implemented; fifty-five are not. ~~Fifty-four of those carry a stated reason~~ All fifty-five carry a stated reason — corrected 6 September 2026; see the note under Implemented elsewhere below — reproduced here. A weaker version of any of them, shipped quietly under the real clause number, would be the one defect this package could not survive: a citation a reviewer relies on that does not check what the clause says.

No record of the thing being checked exists in a submission at all — items 001, 002, 003, 005, 007, 009 (submittal transmittals, scheduler qualifications, software capability, SEKO minutes); 004, 006 (the exporting P6 version — the snapshot carries a file name, not a schema version string); 018, 021, 063, 068, 074, 080, 086, 087, 089, 092 (content-completeness and administrative judgments with nothing in the schedule to assemble); 045 (narrative text is not part of a submission — UFGS-067 assembles what the narrative would have to address, but cannot read the narrative); 082 (the TIA base-schedule test needs "last approved" as a property of a snapshot; the model carries is_baseline, which is a different fact); 084 (fragnet identity is not carried, so an added sequence cannot be traced to the fragnet that was approved); 078 (needs the draft and the resubmission distinguished from each other, and nothing separates them).

The model carries no cost, resource or activity-code data — items 010, 012, 013, 014, 015, 054, 056, 057, 058, 061, 070, 075, 100 (cost loading, CLIN reconciliation, front-end loading, the cost-loading floors); 022 through 030 (the entire SDEF activity coding dictionary — an activity has no codes, and cardinality checks over an absent field would pass every schedule, which is the worst possible result); 093 (activity-code scope, same reason).

The P6 setting is not modelled, and guessing a field name is guessing — item 042 (the percent-complete / remaining-duration linkage is a client preference, not persisted per activity); 094, 095, 096 (calendar scope, duration type, percent-complete type); 103 (the daily ending hour — the calendar carries hours per day, not shift times, so "all calendars end at the same hour" has nothing to compare).

Implemented elsewhere, and duplicating it would double-count the same defect — item 065 (same predicate as 043); 069, 072 (report artefacts and milestone presence, covered by 034/035/037/038). Item 073 (§3.5.4.3, "the critical path is the longest path") belongs in this group: a second enforcement point for §3.12(f), which runs as UFGS-098, and running both would report one defect twice under two clause numbers, which reads to a reviewer as two problems.

~~but it is the one unimplemented item the pack does not list a reason for~~ Corrected 6 September 2026. rules/ufgs.py's not-implemented header lists 073 with that reason, in those words. LIMITATIONS.md §9 item 5 recorded the gap as fixed"the pack's header now says so" — and the correction had not travelled to this page, so this section went on counting fifty-four reasons and naming the fifty-fifth as missing.

No period stated in the specification — item 090 (the recovery-schedule submission clause states no deadline; a number here would be invented).

Partially implemented — item 097 (UFGS-097, §3.12.e): the calendar half (8.0 hours per day) is checked; the P6 Time Period admin preferences 8/40/172/2000 are not in the model. The finding says so, rather than only this document.

What that means in practice

If your review covers cost loading, SDEF activity coding, resource loading, scheduler qualifications, submittal transmittals, SEKO meeting minutes, narrative content, or P6 admin preferences, the tool has told you nothing about any of them. Those checks remain entirely manual.

ASCE 67-17

All 35 guidelines are implemented as rules. But only two of them can be decided from a schedule file, so on a run with no --terms file, 30 of the 35 report NOT_EVALUATED, three are REFERRED to a person, and two are decided. ~~32 of 35 report NOT_EVALUATED~~ — corrected 6 September 2026, measured at 30 on five different files, one of them the cross-validation fixture and four from the corpus. The three that refer rather than abstain are 7.3, 9.6 and 11.4, which want a judgment and not a term, so no --terms file moves them; the two that decide are 5.1 (negative float on the longest path) and 5.3 (constraints altering the critical path), which are facts about the network.

That is not a partial implementation; it is the standard's subject matter. ASCE 67-17 asks what the analyst did, and the analyst has to say.

Both packs


5. Supplying contract terms

The three tables in this section are not the whole terms surface, and were read as though they were until 6 September 2026. cli.py::known_terms accepts 149 keys across the fourteen packs; the tables below name 44 of them — UFGS 01 32 01.00 10, ANSI/ASCE/CI 67-17 and the Source Validation Protocols, which is what a US federal conformance run needs plus the SVP. The other hundred belong to packs reached with --contract or --review and to AACE RP 29R-03, which runs by default under every jurisdiction: 43 aace. keys, 19 scl., 2 wisdot. and 41 unnamespaced state-DOT, NAVFAC, Caltrans, GAO, WVDOH and weather keys. None of them appears on this page.

So the list that cannot go stale is the run itself: every clause that wants a term names its key in the report's Not evaluated section, and unanswered groups the same abstentions by the input that would settle them. Read a no-terms run first and write the file from what it asks for. The tables below tell you what each key means, which the report does not, and that is what they are for.

--terms FILE.json takes a flat JSON object. Keys are read verbatim. A key no rule reads ~~is ignored silently~~ is complained about on standard error, by name, with the nearest real key offered — corrected 6 September 2026, against a run this page's own sentence would have sent a reviewer past. Measured:

$ construct check ... --terms typo.json          # {"contract_completion_dat": ...}
--terms typo.json: no rule reads 'contract_completion_dat' -- did you mean
'contract_completion_date'? It was ignored, and the clause that wants it will
report it as not supplied

The key is still ignored, and the clause that wanted it still reports NOT_EVALUATED — so the second half of the old sentence holds and the warning is what tells the two apart. It goes to stderr and the run continues, which means a reviewer who redirects stdout to a file and reads only the file sees the NOT_EVALUATED and not the reason for it. A key stated twice in the same object is refused outright, exit 2, rather than resolved to one of its two values. cli.py::term_complaints is the implementation.

Dates are written as ISO strings ("2025-06-01") and converted at the edge. Only unambiguous ten-character ISO dates are converted; a string that merely looks date-shaped is left as a string, so a term that is genuinely a string cannot change type without anyone asking.

An empty list is not the same as an absent key. "approved_activity_changes": [] means "the Contracting Officer approved nothing", which is an answer, and a reviewer who states it gets a decided rule. Omitting the key means "nobody said", and gets NOT_EVALUATED.

UFGS 01 32 01.00 10 terms

Key Type Used by What it is
pay_period_thru_date date UFGS-011 §3.2 The "Pay Period Thru" date on the payment request
procurement_activity_ids list of activity ids UFGS-017 §3.3.2 Procurement activities, which §3.3.2 exempts from the duration maximum
long_lead_activity_ids list of activity ids UFGS-019 §3.3.4 Which activities the submission identifies as long lead procurement
mandatory_activity_names list of strings UFGS-020 §3.3.5 The titles §3.3.5's fifteen required activity classes take in this project's naming convention, as agreed at the SEKO meeting
approved_constraint_activity_ids list of activity ids UFGS-033 §3.3.8 Activities whose constraint the Contracting Officer approved
ntp_ack_date date UFGS-034 §3.3.8.1, UFGS-060 §3.4.1, UFGS-062 §3.4.2 The date the notice to proceed was acknowledged
contract_completion_date date UFGS-035 §3.3.8.2, UFGS-059 §3.3.21, UFGS-085 §3.8.6 The date the contract requires completion
interim_completion_dates {phase: date}, or records with phase_id/phase and date UFGS-036 §3.3.8.3 The contractually specified interim completion date for each phase
qc_daily_reports list of records {task_id, date, event} UFGS-043 §3.3.12 The Contractor Quality Control daily reports. activity_id is accepted in place of task_id
approved_out_of_sequence_activity_ids list of activity ids UFGS-044 §3.3.13 Activities whose out-of-sequence progress was approved
approved_activity_changes list of activity ids UFGS-046, UFGS-047 §3.3.14 Activity additions, deletions and description changes the Contracting Officer approved
approved_duration_changes list of activity ids UFGS-048 §3.3.15 Activities whose original duration change was approved
preliminary_submission_date date UFGS-060 §3.4.1 When the Preliminary Project Schedule was submitted (15-day limit)
initial_submission_date date UFGS-062 §3.4.2 When the Initial Project Schedule was submitted (42-day limit)
naming_convention_regex string (regular expression) UFGS-066 §3.5.1 The file naming convention agreed at the SEKO meeting
meeting_log list of records UFGS-076, UFGS-077 §3.6.1, UFGS-079 §3.6.2 Periodic update meetings. UFGS-076 reads {date, proposed_data_date}; UFGS-077 reads {date, submission_date}; UFGS-079 reads {date, update_submission_date}
delay_occurrence_date date UFGS-081 §3.8 When the delay occurred (10-day justification limit)
time_extension_request_date date UFGS-081 §3.8 When the time extension request was submitted
recovery_plan_snapshot string UFGS-088 §3.9.1 The label of the snapshot submitted as the recovery plan

ANSI/ASCE/CI 67-17 terms

All ASCE keys are namespaced asce..

Key Type Guidelines
asce.method MIP section number or common name 4.1
asce.delay_measure_basis "completion_date_change" or "event_duration" 4.4
asce.contract_completion_date date 4.3, 6.2
asce.current_adjusted_completion_date date — the contract completion date adjusted for prior excusable delay 4.3, 4.6, 7.2
asce.scheduled_completion_date date — the schedule's current forecast completion 4.6, 6.2
asce.delay_events list of records; see below 4.5, 4.7, 7.1, 7.2, 7.4, 8.1, 8.2, 8.3, 9.1, 9.2, 9.4, 9.5
asce.apportionment_basis mapping keyed by event id, or "all" 4.7, 8.2, 8.3
asce.damaged_milestones activity ids carrying their own delay damages 5.4
asce.milestone_analyses milestone ids for which a separate analysis was performed 5.4
asce.float_ownership "project", "owner", "contractor", or the clause text 5.2
asce.early_completion_plan mapping with planned_completion_date, owner_reviewed, reviewed_on 6.1, 6.2
asce.time_extension_claimed boolean 6.2
asce.remedy_granting_clause citation string 9.3
asce.technical_expert_reliance records with discipline and question 9.5
asce.legal_review mapping with reviewed_by and jurisdiction 9.6
asce.rectifications list of records; see below. An empty list means none were made 10.1, 10.2, 10.3, 10.5
asce.schedules_considered snapshot labels the analysis actually considered 10.4
asce.abandonment_reasons mapping keyed by label — why each dropped snapshot was abandoned rather than corrected 10.4
asce.acceleration list of records; see below 11.1, 11.2, 11.3

Note that asce.method is resolved through the MIP taxonomy, so supplying "TIA" will make the rule report the ambiguity rather than pick a method. Supply "3.6" or "3.7".

asce.delay_events record keys

Key What it is
id An identifier the reviewer can trace to the record
start, end The dates the delay began and ended
critical Whether the delay was critical when it occurred (8.1)
excusable Whether it entitles the contractor to time (4.5)
compensable Whether it entitles the contractor to money (4.5, 4.7)
responsibility "owner", "contractor" or "third_party" (9.1)
evidence_basis How responsibility was established: records, interview, deposition, expert (9.1, 9.4)
records Contemporaneous record references supporting the chronology (7.4)
notice_given Whether notice of delay was given for an excusable event (9.2)
evaluated Whether this event was carried through the entitlement analysis (7.2)
evaluation_order The position at which the analysis evaluated it (7.1)
apportioned Whether concurrency involving this event was apportioned (8.2, 8.3)
technical_question Any non-scheduling technical question the conclusion rests on (9.5)

An absent key is treated as unstated, never as false. An event with no critical key has not been found non-critical; it has not been assessed.

asce.rectifications record keys

id, snapshot, target (what was changed — activity, link, calendar or date), reason, shown_inaccurate_by (the evidence rebutting the presumption of correctness, per 10.1), error_class ("logic_error", "missing_scope", "incorrect_duration" or other), constraint_class ("physical", "contractual", "preferential" or "situational", per 10.5).

asce.acceleration record keys

id, kind ("voluntary", "directed" or "constructive"), compensation_claimed, agreed_on, started_on, revised_completion_date, cost_basis, and elements — a mapping of booleans covering the five constructive-acceleration elements: excusable_delay, time_extension_requested, owner_denied_or_failed_to_act, owner_insisted_and_contractor_notified, additional_cost_incurred.

Source Validation Protocol terms

forensic.svp is called from the library rather than from the conformance command, but it reads terms from the same object.

Key What it is
notice_to_proceed date — NTP or the contractually recognised project start (SVP 2.1.B.3)
contract_milestones {activity_id: date} — contract milestone dates (SVP 2.1.B.7)
baseline_submittal_log Evidence that this baseline is the earliest conformed plan, or the documented deviation (SVP 2.1.B.1)
rectification_log The record of changes made to the baseline during validation, with the basis for each (SVP 2.1.B.9)
contract_scope Whether the activity set covers the whole of the contracted work (SVP 2.1.B.6)
contract_terms Whether the schedule breaches any other contract term — phasing, access, sequencing restrictions (SVP 2.1.B.8)

update_submittal_log used to appear in this table and has been removed from both the table and REQUIRED_TERMS. Nothing reads it: the check that would — distinguishing officially submitted updates from unsubmitted working copies, SVP 2.3.B — is not built. Listing an input nobody consumes is worse than omitting it, because a reviewer supplies the evidence, nothing happens, and they conclude the tool ignored it. See LIMITATIONS.md §9.

A minimal example

{
  "ntp_ack_date": "2025-01-06",
  "preliminary_submission_date": "2025-01-17",
  "initial_submission_date": "2025-02-14",
  "contract_completion_date": "2026-11-30",
  "pay_period_thru_date": "2025-06-30",
  "approved_constraint_activity_ids": [],
  "approved_activity_changes": [],
  "approved_duration_changes": [],
  "asce.method": "3.3",
  "asce.float_ownership": "project",
  "asce.rectifications": []
}

~~Six of those values are dates~~ Five of those eleven values are dates from the contract file — recounted 6 September 2026. Four are statements that nothing was approved, nothing was changed and nothing was rectified — which are answers, and which decide their rules. The last two, asce.method and asce.float_ownership, are neither: they state what the analysis did and what the contract says about float, and no schedule file carries either.

On the sample run in READING-A-REPORT.md §7 — the three-activity file under --jurisdiction us_federal — that file moves ten clauses out of NOT_EVALUATED, taking the abstentions from 79 to 69 and the decided count from 28 to 37. Measured 6 September 2026, on the file that section describes.


6. Conformance and the Source Validation Protocols are different jobs

Conformance asks does this submission satisfy the specification. SVP asks is this schedule good enough to analyse at all. They are separate, and running conformance rules over an unvalidated baseline verifies the internal consistency of something nobody has checked.

forensic.svp.validate() runs the SVP checks in dependency order — baseline first, then the update chain, then as-planned/as-built correlation — because an update chain validated against an unvalidated baseline has verified its own internal consistency and nothing else.

Of SVP 2.1.B's eleven baseline checklist items, four are decidable from the file: no progress data at or after the data date (SVP-2.1.B.3-PROGRESS), a continuous longest-path chain (SVP-2.1.B.4), no open ends (SVP-2.1.B.5), and the source file's scheduling settings carried through (SVP-2.1.B.11).

~~a data date at or before NTP~~ is not one of them, corrected 6 September 2026SVP-2.1.B.3-DATA-DATE abstains with needs terms['notice_to_proceed'], because NTP is a contract date and no schedule file carries it. The count of four was right and the list was wrong, which is the worse of the two: a reviewer reading it would have expected the NTP test to run on the file alone and would not have gone looking for the term.

The RP's open-end rule is stricter than DCMA's — every activity except the start and finish milestones needs a predecessor and a successor, with no tolerance percentage — so it is computed directly rather than read off a DCMA health report. The remaining seven need the contract, the submittal log or a person, and are emitted as NOT_EVALUATED or REFERRED naming exactly what would settle them. Dropping them would produce a checklist that looks complete and covers four items.

SVP is exposed as python -m forensic validate baseline.xer update-*.xer, which takes --baseline N and --terms like check does. The library API is the same procedure over the same checks, but ~~takes the same arguments~~ takes one already-built Submissionforensic.svp.validate(submission), one parameter — and the subcommand's work is reading the files, choosing the baseline and coercing the terms into one. Corrected 6 September 2026.

Source: docs/CONFORMANCE.md. Source commit date: 2026-09-06.

See it in practice

Follow the evidence, from the schedule to the finding.

Explore the worked example