Running a rule¶
You have a rule and a scenario. This page is how you run them, what the answer looks like, and why a rule sometimes does not run at all.
There are two ways a rule runs, and they are the same engine:
| what feeds it | what you get | |
|---|---|---|
| The authoring loop | your scenario's small, readable dataset | a pass or a failure, in seconds |
| The validator | a real study | a report |
You will live in the first one. The second is how the rule is eventually used.
You do not run your YAML¶
This is the one piece of plumbing worth knowing, because every "I fixed it and nothing changed" traces back to it. The engine never reads the file you edit:
rules-src/checks/CDISC/CDISC-CG0270.yaml what you author
rules-src/docs/CDISC/CDISC-CG0270.yaml
|
| assembly — membership decided by each rule's Standards block
v
target/rules/rules-cdisc-sdtmig-3-4.json what the engine loads
target/rules/packages.json
Rules are authored one file at a time and loaded a package at a time. The assembly step in
between decides which packages your rule belongs to, from its
Standards block.
The build does this for you. Assembly runs as part of the build, before the tests, and
writes into target/. So every test run already has a corpus current with your sources, and
there is no separate "regenerate" step to remember.
The generated corpus is not committed, and that is deliberate. An artefact that cannot fall behind its source cannot go stale. Do not hand-edit anything under
target/rules/— the next build overwrites it, and the rule you meant to change is still wrong.
The authoring loop¶
Run one rule's scenarios by filtering to its identifier:
mvn -o test -Dtest=RuleTestSuitesCdiscFactoryTest -Dscenario.filter=CDISC-CG0270
That is the whole loop: edit the rule or the scenario, run this, read the result. On this tree it takes about eight seconds, of which the scenarios themselves are one.
-Dtest=… picks the family. Scenarios are organised by rule family, one subtree and one
test class each, and the class must match the family your rule is in:
| rule ids | scenarios live in | run with |
|---|---|---|
CDISC-… |
cdisc/ |
RuleTestSuitesCdiscFactoryTest |
FDA-… |
fda/ |
RuleTestSuitesFdaFactoryTest |
PMDA-… |
pmda/ |
RuleTestSuitesPmdaFactoryTest |
DRAFT-… |
draft/ |
RuleTestSuitesDraftFactoryTest |
-Dscenario.filter=… picks the rule. It matches Core.Id prefixes, and takes a
comma-separated list — so -Dscenario.filter=CDISC-CG0270,FDA-CT2001 runs two rules, and
-Dscenario.filter=CDISC-CG02 runs every rule whose id starts that way. Omit it entirely and
the family's whole suite runs.
⚠⚠ A filter that matches nothing is a silent, green pass. Measured: a mistyped id reports
Tests run: 0andBUILD SUCCESS, and the build exits 0 — exactly like a run where everything passed.Tests run: 0, Failures: 0, Errors: 0, Skipped: 0 [INFO] BUILD SUCCESSRead the count, not the colour. A typo in the id, or the right id against the wrong family class, both land here — and "my rule passes now" is the conclusion they invite. Check that the number of tests matches the number of
.cdtfiles you expect to have run.
Reading what comes back¶
A scenario asserts the rule's execution state before it asks about violations, so a failure names the first thing that is wrong rather than a symptom of it. There are four states, and the message tells you which one you are in:
| the rule | you will read |
|---|---|
| ran, found nothing | expected a violation but got none |
| ran, fired | expected no violation but got N violation(s) |
| did not run | the engine's own skip reason, quoted back to you |
| failed | the error, and a note that expect=executionError exists if the failure is the contract |
That third row is the one that saves time. A rule which never ran and a rule which ran and found nothing both report zero violations, and the harness tells them apart — so a scenario that quietly stops exercising anything says so, instead of passing.
Scenarios covers the verdicts themselves, and how to pin where and how often a rule fires.
When a rule does not run at all¶
Five mechanisms, roughly in the order they bite. Each is deliberate; none is an error.
1. The rule is parked. Executability: "Not Executable" removes the rule at assembly — it
never reaches a package, so nothing can run it. The build says so, by name:
[assemble] 3 rule(s) parked by Executability: "Not Executable", …
2. The rule is not in the package. Membership comes from the
Standards block. A scenario that declares a standard and version
checks that the rule is actually a member of that package — so a rule missing the standard it
is being tested against fails there, rather than silently never running.
3. A requirement is not met. A variable listed in
Requirements.Variables is absent, or a required dataset is
not there. The rule is skipped, with a stated reason. This is the intended behaviour, and
it is what expect=skipped exists to assert.
4. The scope does not match. The dataset is not in the rule's
Scope — wrong domain, wrong data structure, wrong class.
5. Every check level is below the run threshold. A run evaluates levels at or above its
threshold, and a rule with nothing at or above it is skipped, again with the reason stated. The
default threshold is Warning, so an Info-only rule does not run unless the run asks for it.
→ Outcome, severity and sensitivity
A rule that needs metadata it was not given is skipped, not failed. If your rule reads CDISC Library metadata, a Define-XML overlay or an external dictionary, the scenario has to supply it — otherwise the rule skips, and an
expect=violationscenario fails with "the rule never executed" rather than with anything naming the metadata. Scenarios lists the directives.
Running against a real study¶
The validator is the command-line face of the same engine. Its smallest useful form names a rule package and some data:
./run.sh --rules-package cdisc-sdtmig-3-4 --data /path/to/study
A package's short name is its file name without the rules- prefix and the extension:
rules-cdisc-sdtmig-3-4.json is cdisc-sdtmig-3-4. --rules-package is repeatable and accepts
a comma-separated list.
The options you will reach for while developing a rule:
| option | what it does |
|---|---|
--rules <id> |
run only matching rule ids — the validator's answer to scenario.filter |
--rules-dir <dir> |
load the corpus from here instead of the bundled one |
--output <file> |
where the report goes (it defaults to a timestamped name) |
--output-format <fmt> |
repeatable; json unless you say otherwise |
--severity-level <level> |
the run threshold — the weakest check level evaluated |
--rules-dir is what points the validator at a corpus you just built, rather than the one
shipped in the bundle. COREJ_RULES_DIR and the corej.rules.dir system property set the same
thing.
⚠
--rulesand--rules-dirare different options one letter apart in intent. One filters which rules run; the other says where rules are loaded from. Passing a directory to--rulesgives you a run that matches no rule — and a clean report.⚠⚠ A successful run exits 0 whether or not it found anything. The exit code reports whether the validator worked, not whether the data conformed:
0success,1a run that failed,2a usage error. A pipeline that treats exit 0 as "the study is clean" will pass every study it ever sees. Read the report, or the finding count the run prints.
Which report formats are available depends on which writer modules the bundle carries — the
validator discovers them at startup, and --output-format with no valid value lists what it
found.
Next: Identity — the authoring chapters, in the order you fill a rule in.