Stages and gates¶
A plant advances through the stages listed in plant.yaml (the template: concept, basic,
detail). Each stage has gates in checks/checks.yaml; cheme check evaluates them and
cheme check --write signs a stage off in checks/status.json.
The project stage is one of four axes, kept apart: it is neither the calculation maturity
cheme maturity evaluates, nor the design maturity a process-library manifest claims, nor a
review status; see the glossary.
The template's STAGES.md defines what each stage requires:
Stages¶
The stages are listed in plant.yaml; the gates of each are in checks/checks.yaml. A gate on
P&ID content is kind: on-sheet (tags and classes found in the model), never a grep over the
XML: DEXPI builds printed tags such as AIA-3103 from several attributes, so grep finds ids,
not tags.
A stage is reached when its sign-off (cheme check --stage <stage> --write, an entry in
checks/status.json) is on the default branch. Stage work is done on a branch
stage/<stage>-<topic>; the sign-off is its last commit, and the branch is merged by a human or
the reviewer, not by its author (see AGENTS.md). A sign-off left on an unmerged branch has not
taken effect.
concept: Concept and feasibility¶
Design basis agreed (assumed values marked ASSUMED), block flow (process/flowsheet.yaml, the
generated BFD, balances evaluable and clean), main equipment identified, the equipment, line and
instrument lists and DESIGN.md started (gates design-basis, concept-lists,
assumptions-marked, process-concept, process-balances; process-links-concept reports
unresolved links). The plot plan: the site, its areas, roads and pipe racks, every
equipment-list item placed and the spacing criteria declared as separations: (gate
layout-plot-plan, skill layout-yaml). Economics: the decision the next stage needs stated,
an AACE class 5 capital estimate, the prices of the main feeds and products with their sources,
and a screening TEA showing which premises dominate (estimate/tea.yaml, skill tea).
basic: Basic engineering¶
PFD generated with no refinement pending and no stale calculated values (process-basic), every
flowsheet link resolved (process-links), P&IDs complete and valid, equipment and line lists
complete, design review done. The 3D layout (gate layout-3d): layout/plant-model.yaml
built from the current inputs and committed, every piped nozzle positioned (from the P&ID side
or an override), every elevated item mounted on a building or structure level, every process
line of DN 25 and above routed or skipped with a reason (utilities follow at detail), no clash
or headroom finding left unaccepted, zones declared (layout/zones.yaml, skill
area-classification), and a layout review (topic layout, gate layout-review) that cites
the rendered plot plan and at least two sections (skills pipe-routing, structures-access,
review). The material take-off (estimate/mto.csv, skill mto-estimate) is rewritten from
the committed model (gate mto). Economics: a class 4 capital estimate (equipment costed from
datasheet sizes, OSBL and owner's cost stated), every flow priced or listed free, the TEA's
uncertainty and break-even values reviewed against the decision (gate tea, skill tea).
detail: Detail engineering¶
Datasheets (one equipment/datasheets/<TAG>.yaml per equipment-list item, format in
equipment/datasheets/README.md, cheme schema datasheet; gate datasheets), instrument
list, HAZOP actions closed. Every line routed, utilities and small bore included, still
without clashes (gate layout-detail). Every motor and instrument has a cable routed in trays
from its room, no tray over its fill, power and instrument trays kept apart
(electrical/electrical-list.yaml, skill electrical-cabling; gate electrical). Every
clad building is ventilated: air handling units, rooms with their air changes, ducts routed
inside the building within their velocity limits, louvres large enough (layout/hvac.yaml,
skill hvac-ducts; gate hvac). Where the plant documents its control: every loop and
sequence complete and every control reference resolved (control/loops.yaml,
control/sequences.yaml, process/narrative.md, control/philosophy.md; skills
control-loops, control-sequences, control-narrative; gate control-complete).
Economics: a class 3 estimate from quotations and the take-off, the TEA current with it, ready
for the investment decision.
check¶
cheme check [--stage S [--only-stage]] [--write [--review RECORD] [--by NAME]] evaluates
the gates of checks/checks.yaml and exits 1 when one fails. --stage S runs the gates of every
stage up to S in plant.yaml order (concept and basic for basic); --only-stage only S's. A
gate whose stage: is not a stage of plant.yaml (a typo such as Concept) is never left out:
it fails in every run, and cheme validate reports it (cheme.check-stage-unknown, error), as
it does a check id listed twice (cheme.check-duplicate-id, error). It only prints, except
with --write, which signs off stage S (default: plant.yaml's stage) in
checks/status.json:
{
"stages": {
"basic": {
"stage": "basic", "commit": "<sha of the checked tree>", "date": "2026-10-18T14:02:11Z",
"signed_by": "A. Engineer", "branch": "stage/basic-closeout", "default_branch": "main",
"review": "reviews/2026-10-18-basic.md", "review_commit": "<sha>",
"reviewer": "...", "reviewer_kind": "human", "stages": ["concept", "basic"],
"checks": [{"id": "pids-valid", "title": "...", "stage": "basic", "status": "pass", "detail": ""}]
}
},
"checks": ["... the gates of the latest sign-off, as 0.3 wrote them"]
}
Each stage has its own entry; signing off basic keeps the concept entry. --write writes
nothing and exits 1 when the working tree has changes other than checks/status.json, when any
gate fails, when no signer is known (--by, else git config user.name), when the --review
record has no valid front matter or is the signer's own (reviewer_kind: self, or the reviewer
is the signer) without an approved_by naming someone else, or when a critical or high finding
of any review record is open or is closed by a commit that does not exist or is not an
ancestor of HEAD, or when the --review record has cheme.review-* warnings. A finding is as
severe as the most severe rating any record gives it: a later record that carries it forward
without a severity, or with a lower one, does not lower it (close, accept or supersede it
instead); an open critical or high row whose id is not <record>#<n> blocks on its own.
cheme status and GET /api/status show the same severity. Signed off on a branch other than
the default, it says to merge: a stage counts as signed off only once the sign-off is on the
default branch. Commit
checks/status.json as the last commit of the stage. cheme schema status prints the
file's schema.
| kind | passes when |
|---|---|
validate |
no cheme validate finding at fail_on (error, the default; warning; info) or above, among the rules (ids, * matches; default all), that neither the gate's accept: [{id, reason}] nor the repository-wide accept: list names (errors are never accepted); a gate that cheme.generated-sheet-drift could fail runs the P&ID generators, as cheme validate does, and fails on cheme.generator-failed (a generator that cannot run) as on drift: a rule that matches the drift (cheme.generated-* included) matches it too |
files |
every glob of files matches a file inside the repository |
command |
the shell command, run at the repository root, exits 0 |
on-sheet |
the tags are drawn on the sheet (or each of sheets; neither: any sheet), and at least min_count (1) items of each of classes |
datasheets |
every equipment-list item has a valid equipment/datasheets/<TAG>.yaml (cheme schema datasheet; an empty list fails) |
layout-model |
the 3D layout meets each item of require: site, areas, roads, racks, placed (every equipment-list item), separations (declared in the site plan), model-current (layout/plant-model.yaml built from the current inputs), nozzles (no unknown override, no piped nozzle placed by default), mounted (every item above grade on a building or structure level, or on another item), routed (every line of DN min_dn (25) and above, not matched by exclude_lines / exclude_fluids, routed or skipped with a reason), clashes (no model finding of rules, default cheme.layout-clash-*, -headroom and -buried-cover, unaccepted), zones (declared), drawings (a review record of topic, default layout, cites the plot plan and sections (2) sections) |
review |
a review record for the topic (or among the files globs) has valid front matter, a reviewer kind in require_kind (default [human, agent]: a self review does not pass), a reviewed_commit that is an ancestor of HEAD, and (current: true, the default) no file of its scope changed since that commit, committed or not (when git cannot tell, the record is not current) |
The template's drawing-clean gate (stage basic) fails while a drawing finding is neither fixed
nor accepted; the detail of a failing validate gate counts what failed per rule and names the
first finding's id:
- id: drawing-clean
title: No line through a label or valve, along a pipe or slanted, no crossing, overlapping or
misplaced label left unaccepted
stage: basic
kind: validate
fail_on: info # cheme.line-not-orthogonal is an info finding
rules: [cheme.line-through-label, cheme.line-crossing, cheme.label-overlap,
cheme.label-far-from-owner, cheme.line-through-valve, cheme.line-on-pipe,
cheme.line-not-orthogonal]
on-sheet asks the loaded model, not the XML text: a tag is the one the P&ID prints
(TagName, the instrument tag AIA-3103 built from category, letters and number, a piping
component's first PipingComponentNumber / PositionNumber / PipingComponentName that is not
a segment-local C1, a LineNumber), so renaming ids changes
nothing and an id or substring that merely looks like the tag does not pass. A class matches
itself, its DEXPI superclasses and classes whose name ends with it (SafetyValve matches
SpringLoadedGlobeSafetyValve). With connected: true each item must also be connected:
equipment with a pipe at a nozzle, an instrument function with a measuring or signal line (or a
sensing location), a piping component on a segment with a pipe or the segment's end at it. A
failing gate names what is missing, e.g. P-03: AIA-9999 not drawn; FV-3111 drawn but not
connected.
A review record is reviews/<YYYY-MM-DD>-<topic>.md starting with YAML front matter
(cheme schema review):
---
topic: o2-safety
reviewed_commit: 521473c0d3... # the commit whose files were reviewed
scope: [pid/P-03.xml, pid/P-04.xml, DESIGN.md]
reviewer: A. Engineer
reviewer_kind: human # human | agent | self (the author's own review)
---
A passing review gate names the record and its commit, (agent-reviewed only) when the reviewer
is not a human, and its evidence (path, reviewed_commit, reviewer, reviewer_kind) goes
into checks/status.json. A failing one names the record and why: no front matter, a field
missing, the reviewer kind, not an ancestor of HEAD, or changed since <commit>: pid/P-04.xml.
cheme validate checks every reviews/*.md (but README.md):
| rule | level | finds |
|---|---|---|
cheme.review-front-matter |
warning / info | front matter that is not YAML or misses a field (warning); a record without front matter (info: no review gate accepts it) |
cheme.review-unknown-commit |
warning | a reviewed_commit that is not a commit of the repository |
cheme.review-scope-missing |
warning / info | a scope path that does not exist (warning); one written with a leading / (warning) or ./ (info), which is read from the repository root (/pid/P-01.xml as pid/P-01.xml, / and ./ as ., the whole repository) |
cheme.review-finding-id |
warning | a finding id that is not <record file stem>#<n> (an earlier record's id under Previous findings), or names no record |
cheme.review-finding-status |
warning / info | a status other than open, closed <commit> (an existing commit), accepted: <reason>, superseded by <id>; a severity other than critical, high, medium, low (info); an open finding without a severity (info); an open finding carried forward with no or a lower severity than the critical or high an earlier record gave it (warning: it still counts as that) |
cheme.review-previous-missing |
warning | the newest (or an uncommitted) record does not list, under ## Previous findings, a finding of an earlier record of overlapping scope that is still open |
A findings table is any Markdown table with an id and a status column. A finding's status
is the one in the latest record listing it (by date, then the commit that added the record);
records are snapshots, and the next record carries open findings forward with their new status.
- id: hydrocarbon-on-pid
title: Sump hydrocarbon analyzer and LOX purge drawn
stage: basic
kind: on-sheet
sheet: P-03
tags: [AIA-3103, FIC-3111, FV-3111]
classes: [SafetyValve]
connected: true
status¶
cheme status [--json] [--rev REV] [--no-gates] summarizes where the repository stands,
read-only: the stage; per sheet whether it is drawn, empty (an empty frame), missing
(declared in plant.yaml, no file yet) or not-loaded, what it draws and which items the lists
assign to it (sheet:) that it does not draw; the next sheet to draw; validation totals by level
and rule, and the not-on-pid findings by the sheet their list entry names (unassigned without
sheet:); every stage's gates (evaluated, not written; --no-gates skips them, as they may run
commands); the sign-offs of checks/status.json, flagged when the signed commit is not on the
default branch; the open review findings; and git branch, head, uncommitted paths (null in
--json when git cannot tell) and how far the branch is ahead of / behind the default branch.
--rev summarizes a revision, checked out in a temporary worktree that is removed afterwards. --json has the keys plant, sheets
(id, file, state, drawn, assigned, missing), next_sheet, validation, gates,
signoffs, legacy_status (a 0.3 status.json), reviews and git, documented in
chemeascode.project.status; a script can take the next sheet to draw from next_sheet. GET
/api/status returns it (without the gates).