cwright lint
cwright lint <model> [--json]
Asks whether what the document describes is possible — and says so without refusing.
validate answers is this document well formed; lint answers can this
model run at all, which is a different question with a different remedy. A step naming a
resource that does not exist is a typo. A tool group the route data demands 112% of is a
decision somebody has to make.
It computes four things from the route data alone, with no simulation:
| check | what it compares |
|---|---|
offered-load | the work each resource is asked for, against servers × capacity × availability |
operator-shift | the person-seconds each set of interchangeable pools is asked for, against its headcount |
batch-fill | how long a lot waits for company, against the whole of its own route |
release-rate | the declared release rate, against what the bottleneck sustains |
unfed-store | a consumable a step draws from that starts empty (initial: 0), never reorders and that no step produces — every entity reaching it waits for ever, and the line has no bound |
A model path of - reads the document from stdin.
It warns, and never refuses
lint exits 0 whatever it finds. A transient overload is a legitimate thing to model — a
fab deliberately over-released to see what happens is a normal experiment — and a check that
blocks gets switched off. validate is the command that refuses.
Why the arithmetic and not the run
A simulation cannot tell you how much a resource was over-asked, because a saturated server cannot report more than 100% busy. The over-release comes out somewhere else: as arrivals turned away, as a queue that grows all run, or as an upstream tool blocked.
examples/wafer-fab.yaml is the clearest case, because the fix ships beside it:
litho offered load | litho Idle% | litho mean queue | |
|---|---|---|---|
wafer-fab.yaml | ⚠️ 100.11% | 0.00 | 75.13 |
wafer-fab-3-scanners.yaml | 66.74% | 20.37 | 3.10 |
Its Processing is only 80.23% in the first row — the rest of the tool's time is downtime — so
no utilisation figure in any results document could have said 100%. The arithmetic can.
⚠️ The engine columns are one replication (simulate --replication 1); the point they make is
the shape, not the third digit.
Every reading is the favourable one
Each term takes the interpretation most generous to the model, so anything still flagged is infeasible under every reading:
- a batch step is charged one load's worth of tool time divided by the largest number of lots a load can hold, not one lot's worth;
- maintenance and breakdown come out of the availability multiplicatively, which is the highest availability the two can jointly leave;
- a batch group's wait is measured against the longest route among its members;
- a batch group's minimum is the smallest any member declares;
- changeover time is charged as zero. A perfectly campaigned tool never changes recipe,
so zero is the favourable reading — and how often a tool actually changes over is a
dispatch outcome rather than something the document states. ⚠️ It is not small: measured
on SMT2020's HVLM,
Implant_91spends 10.99% of its life inSetup,Implant_12810.39% andLithoTrack_FE_956.82%. So a group this reports at 0.92 may really need 1.03, and the offered load is a lower bound whereverchangeoversormin_runare declared.
Three terms were missing before the arithmetic worked, and each was found by refusing to believe a number that cannot be true of a fab that demonstrably runs:
- a step's
probabilityis the share of lots that visit it at all — metrology is sampled, and charging every lot reported 396.95% on a fab that runs; - maintenance has to be in the availability, and it is most of the gap the first hand calculation found;
- a usage maintenance trigger counts visits, not pieces. Counting pieces is 25× too large on a 25-wafer lot, and it drove 71 of 106 tool groups to zero availability.
Output
wafer-fab (43570174d106f9ee)
resource slots avail offered load
litho 2.0 0.881 1.7639 100.11% <-- at or above capacity
etch 2.0 0.904 1.0820 59.83%
...
3 warning(s) -- this is not a refusal:
offered-load litho: the route data demands 100.11% of it: 1.7639 slot-seconds of work per
second against 1.7620 slots (2 x availability 0.881)
release-rate logic: the bottleneck is litho at 100.11%, so this stream is 1.00x what the
fab sustains: one lot every 5406 s would fit where it asks for one every 5400 s
release-rate memory: the bottleneck is litho at 100.11%, so this stream is 1.00x what the
fab sustains: one lot every 6407 s would fit where it asks for one every 6400 s
The table is printed even when nothing is flagged, because the useful answer to "can this run?" is usually "yes, and here is the bottleneck".
--json emits the same thing as a document: resources, pools, batches and findings,
each carrying the numbers rather than the sentence. An infinite ratio — nothing feeds this
batch group — is written as the string "Infinity", because JSON has no spelling for it.
What it does not compute
A closed population has no release rate, so the offered-load question does not apply to it:
a conwip model is capacity-limited by construction, and lint says so
rather than reporting 0%. The same goes for a model that releases nothing at all. In both cases
the reason is printed, because an empty table and a table of zeroes read the same and neither
means fine.
Three more things are outside it, and they are outside it silently — so they are listed here rather than discovered:
- changeover time, for the reason above. The offered load is a lower bound on any resource
that declares
changeoversor a campaign minimum. - consumable supply. A store whose replenishment cannot keep up with
the draws against it is one division away — and it is not done.
examples/gas-station-tank.yamlwith its reorder quantity at 3,000 units and an 18,000-second lead time supplies 0.167 units a second against a demand of 0.706, 4.2× over, andlintprints the same table it prints for the healthy model and forgas-station.yaml, which has no tank at all. The engine's answer for that document is 5,079 lots completed of 6,742 released, with 6,198 stockouts. - a scheduled lot before the measured window. Work that arrives and clears during warm-up is not work the window has to do, which is the whole reason warm-up exists. SMT2020's 2,256 work-in-progress lots all sit at time zero and therefore contribute nothing.
When to use it
Between validate and run, on any document a script or an agent generated. It costs no
simulation, and the four questions it asks are the ones that produce a model which runs, reports
plausible numbers, and is wrong.