Tutorial: Validating BV4
A walk-through of running adsmd on the tests/BV4 plant project: what each diagnostic means, which ones point at real input problems, and how the other subcommands help when you inspect a project.
Setup
Build the binary once:
cd tools/adsmd
cargo build --release
PATH="$PWD/target/release:$PATH" # convenience for the rest of this pageAll commands on this page are run from tools/adsmd/.
First run
adsmd validate ../../tests/BV4The report lists every diagnostic with its rule id, file:line:col, a source snippet and, where one exists, a help line. It ends with a summary line. On a fresh checkout of the repository the summary is:
adsmd: 1 error(s), 1 warning(s), 166 info(s)and the exit code is 2, because at least one error is present.
| Rule | Severity | Count | What it reports on BV4 |
|---|---|---|---|
R0 | error | 1 | SystemReliability.txt cannot be parsed |
R24 | warning | 1 | SPDS_Tave-Tref is in the OAT safety parameters but not in SPDS_input.txt |
R13 | info | 149 | ZProcedure_*.txt files that are not in the coded-step index |
R45 | info | 9 | initiating events scheduled after Truncation_time |
R32 | info | 6 | .bak and .pre files under Input/ |
R30 | info | 1 | Input/Crew/StaticPIF.txt is not read by the engine |
R33 | info | 1 | the project has no ZiniADS.txt |
The totals depend on ZiniADS.txt
tests/*/ZiniADS.txt is git-ignored, so a fresh checkout of tests/BV4 has none. If your working copy has a local tests/BV4/ZiniADS.txt, the result changes:
R33does not fire, so the run reports 165 infos.R1andR2check the two paths in the file. Wheninput-decknames a file that does not exist,R1adds a second error:2 error(s), 1 warning(s), 165 info(s).
The copy of BV4 that CMake writes to build/tests/BV4 has a generated ZiniADS.txt with a valid deck path; adsmd validate on that directory reports 1 error(s), 1 warning(s), 165 info(s).
Piped to a file or another program, the report uses plain ASCII frames, as in the excerpts below. On a terminal the same frames are drawn with Unicode box characters and colour.
What each diagnostic means
R0: parse error in SystemReliability.txt
adsmd::R0
x could not parse `X_SGTR_SG_A` as a number
,-[…/tests/BV4/Input/HardwareReliability/SystemReliability.txt:2:1]
1 | System_Reliability 0
2 | Main_Steam_Isolation X_SGTR_SG_A 9999999.0 1.0 6.0 4.0
: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
3 | Reactor_Trip X_TD_AFW_Pump 50.0 50.0 50.0 50.0
`----The file declares System_Reliability 0 and then lists 14 rows. With a count of 0 the first subsection is empty, so the next two tokens (Main_Steam_Isolation, X_SGTR_SG_A) are read as the header and the count of the second subsection, and X_SGTR_SG_A is not a number. The engine reads the file the same way: its stream read fails at that token, it prints a WARNING: SystemReliability.txt: malformed or unexpected content line, and it loads no hardware failure models from the file.
In your own project, fix this by making the count on line 1 match the rows that follow and by keeping all three subsection headers (see the format reference). tests/BV4 itself is a regression fixture whose expected outputs encode the current behaviour, so leave its inputs as they are.
A parse error stops only the parser of that file. The file is missing from the manifest, rules that need it (R10, R11, R42 here) have nothing to check, and every other rule still runs.
R24: KB safety parameter missing from SPDS_input.txt
adsmd::R24
! OAT safety-parameter `SPDS_Tave-Tref` is not present in `SPDS_input.txt`
,-[…/tests/BV4/Input/Crew/ActionTaker/KB_OAT_Safety_Parameters.txt:3:1]
2 | SPDS_PZR_Level PZR_Level 0.10 0.35 0.55 0.95 0.5
3 | SPDS_Tave-Tref Tave-Tref -5.0 -1.0 1.0 5.0 0.5
: ^^^^^^^^^^^^^^
`----
help: either add the entry to SPDS_input.txt or remove it from the KBThe action taker's safety-parameter list names an SPDS entry that the central SPDS_input.txt does not define. The JSON and HTML reports also carry up to three "did you mean" candidates for this diagnostic.
R13 × 149: step files outside the coded-step index
Each hit names a ZProcedure_<EOP>_<Step>.txt file whose (EOP, Step) pair has no row in Procedures.txt. On BV4 these are 34 E_0 files, 81 E_3 files, 33 ECA_0.0 files and 1 FRG_H.1 file. The severity is info because such files can be intentional. The .adsmd.toml example below switches this rule off.
R45 × 9: initiating events after Truncation_time
ZCtrlPar.txt sets Truncation_time to 15.5 s, and nine rows of Initiating_Event.txt have time = 850. Those events do not fire in a run that uses the file's truncation time. The engine's --truncation-time option changes the truncation time at run time; adsmd reads the value in ZCtrlPar.txt.
R32 × 6: editor artifacts
Four .bak and two .pre files in Input/Crew/Procedures/. The engine does not read them. R32 lists each one so that it is either removed or kept on purpose.
R30: StaticPIF.txt is unread
No engine code opens Input/Crew/StaticPIF.txt. Edits to it have no effect on a simulation.
R33 and R1: the role of ZiniADS.txt
adsmd recognises a directory as a plant project when it contains ZiniADS.txt, or when it contains both Input/Scheduler/ and Input/System/. In the second case R33 notes that the file is absent, and the RELAP5 deck is not scanned (the deck path comes from ZiniADS.txt), so R29 has nothing to check.
When ZiniADS.txt exists, R1 checks its input-deck path. A file whose input-deck points into Input/ while the deck sits in the project root produces:
adsmd::R1
x input-deck path `…/tests/BV4/Input/BV4.i` does not exist; a file with the same name was found at `…/tests/
| BV4/BV4.i`
,-[…/tests/BV4/ZiniADS.txt:2:1]
1 | project-dir=…/tests/BV4
2 | input-deck=…/tests/BV4/Input/BV4.i
: ^^^^^^^^^^
`----
help: update ZiniADS.txt: `input-deck=…/tests/BV4/BV4.i`The help line gives the corrected entry. After the path is fixed the R1 error is gone and the run reports 1 error(s), 1 warning(s), 165 info(s).
JSON output
For scripts, CI gates and dashboards use --json:
adsmd validate ../../tests/BV4 --json | jq '.summary'{
"errors": 1,
"exit_code": 2,
"infos": 166,
"warnings": 1
}These are the fresh-checkout numbers from the first run. The full list is under .diagnostics:
adsmd validate ../../tests/BV4 --json | jq '.diagnostics[] | {rule_id, severity, message}'A per-rule count, which is how the table in First run was produced:
adsmd validate ../../tests/BV4 --json | jq -r '.diagnostics[] | "R\(.rule_id) \(.severity)"' | sort | uniq -cSARIF output
--sarif emits a SARIF 2.1.0 log for GitHub Code Scanning, Azure DevOps and editors with a SARIF viewer:
adsmd validate ../../tests/BV4 --sarif > adsmd.sarifEach rule that fired appears under runs[0].tool.driver.rules with the id ADSMD<NNNN> (ADSMD0000-PARSE for R0) and a helpUri that points at the rule catalog. A complete GitHub Actions job is in Integrating adsmd.
HTML report
adsmd validate ../../tests/BV4 --html=bv4.htmlwrites one self-contained page (inline CSS and JavaScript, no external assets) with severity and rule filters, per-file grouping and source snippets.
Filtering by severity or rule
--severity <level> keeps only diagnostics at that level or above (error > warning > info):
adsmd validate ../../tests/BV4 --severity erroradsmd: 1 error(s), 0 warning(s), 0 info(s)(On a fresh checkout. A local ZiniADS.txt with a wrong deck path adds the R1 error.)
--rule R1,R7-R12,R29 keeps only the listed rule ids; ranges are inclusive. R0 (parse errors) is always kept, because hiding the parser would hide whole files.
adsmd validate ../../tests/BV4 --rule R1,R7,R10,R11,R29Both filters are applied before the summary and the exit code are computed. A diagnostic that is filtered out does not count toward the exit code.
adsmd rules prints the catalog (id, short name, one-line description):
adsmd rules | head -3R0 PARSE Parser bailed before producing a typed value
R1 input-deck-exists ZiniADS input-deck path resolves
R2 project-dir-layout ZiniADS project-dir + Input/ existExit codes and --exit-on
By default only errors make validate fail: the exit code is 2 when an error is present and 0 otherwise, even when warnings fired. --exit-on warning also returns 1 when the worst finding is a warning, --exit-on info returns 1 for any finding, and --exit-on never always returns 0. On BV4, which has an R0 error:
adsmd validate ../../tests/BV4 --exit-on neverexits 0, and every other --exit-on level exits 2. The complete table is in the CLI reference.
Per-project config: .adsmd.toml
Put a .adsmd.toml in the plant project directory or in any directory above it. adsmd walks up from the project and uses the first file it finds. The [rules] table switches rules off or changes their severity, so a project can keep a clean report without passing --rule on every call.
[rules]
R13 = "off" # silence "ZProcedure not in the coded-step index" notes
R30 = "off" # silence the StaticPIF.txt note
R32 = "warning" # raise .bak/.orig/.pre artifacts from info to warning
R14 = "error" # raise header/filename mismatches from warning to errorWith this file next to a fresh-checkout copy of BV4, adsmd validate reports 1 error(s), 7 warning(s), 10 info(s): the 149 R13 and 1 R30 infos are gone and the 6 R32 findings count as warnings.
Values are case-insensitive: off, info, warning (or warn), error. Keys are R<n>, r<n> or <n>. R0 cannot be configured: a .adsmd.toml that names it, or that holds an unknown value, makes validate stop with exit code 64.
Rules that are switched off do not appear in the output and do not affect the exit code. Rules with a changed severity count at their new severity, so R32 = "warning" together with --exit-on warning makes editor artifacts fail a build.
Inspecting the manifest
adsmd manifest <plant-dir> prints the typed manifest as JSON, which shows what the parsers extracted:
adsmd manifest ../../tests/BV4 | jq '{
iE: .initiating_event.declared_count,
channels_volumes: .channels.control_volumes.declared_count,
cp_params: (.control_panel.parameters | length),
cp_alarms: (
[.control_panel.alarms_parameter,
.control_panel.alarms_component,
.control_panel.alarms_difference] | flatten | length
),
fts: (.fault_trees | length)
}'{
"iE": 21,
"channels_volumes": 20,
"cp_params": 141,
"cp_alarms": 93,
"fts": 2
}On BV4 the command also prints adsmd: 1 parse-error diagnostic(s) emitted to stderr; manifest may be partial on stderr, because SystemReliability.txt does not parse and the manifest has no hw_reliability entry. Only this count line is printed; use adsmd validate to see the parse error itself.
adsmd schema prints the JSON Schema (draft-07) of the manifest, generated from the Rust types together with their documentation comments:
adsmd schema > plant-project.schema.jsonSummary of a project: adsmd stats
adsmd stats ../../tests/BV4prints one line per file family, for example:
Initiating_Event.txt: 21 action(s)
RELAP5_channels.txt: 20 HV / 24 HJ / 51 VT / 31 LT / 99 IC / 0 HS / 78 CV
ControlPanel.txt: 141 param / 77 comp-state / 29 fine / 67 ctrl / 38 alarm-p / 37 alarm-c / 18 alarm-d / 0 support-system
Procedures: 13 multipliers / 302 coded steps / 452 step files / branches: A=8 CV=33 M=4No rules are run. The ZiniADS.txt and Deck lines depend on whether a ZiniADS.txt is present.
Comparing two projects: adsmd diff
adsmd diff ../../tests/BV4 ../../tests/BV3─ ControlPanel.support_system_displays (0 total in a, 2 total in b)
+ FTANode0
+ FTANode6
─ Procedures.coded_steps (302 total in a, 303 total in b)
+ E_0/Step_3The output starts with two lines naming a and b (omitted above). The exit code is 1 when differences exist and 0 when the compared name sets are identical.
Cross-references: adsmd xref
adsmd xref <plant-dir> <symbol> lists every place a name is declared or referenced in the manifest, one hit per line as tab-separated <file>:<line>:<col>, [<context>] and the source line, so an editor can jump to the location:
adsmd xref ../../tests/BV4 X_PZR_PORV…/Input/Indicators/ControlPanel.txt:226:1 [ControlPanel.fine_adjust_controls] X_PZR_PORV IC_805 1.0 0.33 …
…/Input/System/Initiating_Event.txt:8:1 [Initiating_Event.action_symbol] Open_PORV 0.33 850.0 X_PZR_PORV …
…/Input/System/Initiating_Event.txt:13:1 [Initiating_Event.action_symbol] Close_PORV 0.0 -185.0 X_PZR_PORV …(The real output has absolute paths and the full source lines; both are shortened here.)
The symbol is matched literally against channel symbols (HV_204), ControlPanel names (Containment_Pressure, alarm names, X_* control symbols, support-system display names), SPDS names, initiating-event action symbols, SystemReliability.txt names, EOP and step ids (E_0), and Calculation_Aid.txt curve and input names. The context tag identifies the manifest section that matched. The exit code is 1 when nothing matches.
Next steps
- The rule catalog documents every check.
- The format reference describes what each input file should look like.
- Integrating adsmd covers CI, hooks and reports.